From c1f4302e6276386a5f785753fd84a5eb003ab0b1 Mon Sep 17 00:00:00 2001 From: Jan S Date: Tue, 10 Mar 2020 10:11:44 +0100 Subject: [PATCH 01/55] feat(gui): allow to search for full method/field signature, not only the name (PR #880) --- .../main/java/jadx/gui/treemodel/JField.java | 13 ++++++++++++- .../main/java/jadx/gui/treemodel/JMethod.java | 14 +++++++++++++- .../main/java/jadx/gui/treemodel/JNode.java | 10 +++++++++- .../java/jadx/gui/ui/CommonSearchDialog.java | 2 +- .../main/java/jadx/gui/ui/RenameDialog.java | 2 +- .../main/java/jadx/gui/ui/TabComponent.java | 2 +- .../src/main/java/jadx/gui/ui/UsageDialog.java | 2 +- .../src/main/java/jadx/gui/utils/UiUtils.java | 4 ++++ .../jadx/gui/utils/search/TextSearchIndex.java | 18 ++++++++++-------- 9 files changed, 52 insertions(+), 15 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java index d1c2b574a..433ac6f20 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java @@ -1,6 +1,7 @@ package jadx.gui.treemodel; -import javax.swing.*; +import javax.swing.Icon; +import javax.swing.ImageIcon; import jadx.api.JavaField; import jadx.api.JavaNode; @@ -65,11 +66,21 @@ public class JField extends JNode { return UiUtils.typeFormat(field.getName(), field.getType()); } + @Override + public String makeStringHtml() { + return UiUtils.typeFormatHtml(field.getName(), field.getType()); + } + @Override public String makeLongString() { return UiUtils.typeFormat(field.getFullName(), field.getType()); } + @Override + public String makeLongStringHtml() { + return UiUtils.typeFormatHtml(field.getFullName(), field.getType()); + } + @Override public int hashCode() { return field.hashCode(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index 1cef3404f..ffe4e60d3 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -2,7 +2,8 @@ package jadx.gui.treemodel; import java.util.Iterator; -import javax.swing.*; +import javax.swing.Icon; +import javax.swing.ImageIcon; import jadx.api.JavaMethod; import jadx.api.JavaNode; @@ -93,12 +94,23 @@ public class JMethod extends JNode { return UiUtils.typeFormat(makeBaseString(), getReturnType()); } + @Override + public String makeStringHtml() { + return UiUtils.typeFormatHtml(makeBaseString(), getReturnType()); + } + @Override public String makeLongString() { String name = mth.getDeclaringClass().getFullName() + '.' + makeBaseString(); return UiUtils.typeFormat(name, getReturnType()); } + @Override + public String makeLongStringHtml() { + String name = mth.getDeclaringClass().getFullName() + '.' + makeBaseString(); + return UiUtils.typeFormatHtml(name, getReturnType()); + } + @Override public int hashCode() { return mth.hashCode(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 85750bc25..5d15a84fe 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -1,6 +1,6 @@ package jadx.gui.treemodel; -import javax.swing.*; +import javax.swing.Icon; import javax.swing.tree.DefaultMutableTreeNode; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; @@ -77,6 +77,10 @@ public abstract class JNode extends DefaultMutableTreeNode { public abstract String makeString(); + public String makeStringHtml() { + return makeString(); + } + public String makeDescString() { return null; } @@ -89,6 +93,10 @@ public abstract class JNode extends DefaultMutableTreeNode { return makeString(); } + public String makeLongStringHtml() { + return makeString(); + } + @Override public String toString() { return makeString(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 86073aa15..17e6ccf5d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -446,7 +446,7 @@ public abstract class CommonSearchDialog extends JDialog { private Component makeCell(JNode node, int column) { if (column == 0) { - JLabel label = new JLabel(node.makeLongString() + " ", node.getIcon(), SwingConstants.LEFT); + JLabel label = new JLabel(node.makeLongStringHtml() + " ", node.getIcon(), SwingConstants.LEFT); label.setFont(font); label.setOpaque(true); label.setToolTipText(label.getText()); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index b70f87704..06fe2bfbf 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -196,7 +196,7 @@ public class RenameDialog extends JDialog { private void initUI() { JLabel lbl = new JLabel(NLS.str("popup.rename")); - JLabel nodeLabel = new JLabel(this.node.makeLongString(), this.node.getIcon(), SwingConstants.LEFT); + JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT); lbl.setLabelFor(nodeLabel); renameField = new JTextField(40); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java index f53fc91d9..98968dd67 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java @@ -47,7 +47,7 @@ public class TabComponent extends JPanel { panel.setOpaque(false); JNode node = contentPanel.getNode(); - label = new JLabel(node.makeLongString()); + label = new JLabel(node.makeLongStringHtml()); label.setFont(getLabelFont()); String toolTip = contentPanel.getTabTooltip(); if (toolTip != null) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java index cbc0a6860..06d7e5df2 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java @@ -54,7 +54,7 @@ public class UsageDialog extends CommonSearchDialog { private void initUI() { JLabel lbl = new JLabel(NLS.str("usage_dialog.label")); - JLabel nodeLabel = new JLabel(this.node.makeLongString(), this.node.getIcon(), SwingConstants.LEFT); + JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT); lbl.setLabelFor(nodeLabel); JPanel searchPane = new JPanel(); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index 54fb89b15..5b62e6777 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -65,6 +65,10 @@ public class UiUtils { } public static String typeFormat(String name, ArgType type) { + return name + typeStr(type); + } + + public static String typeFormatHtml(String name, ArgType type) { return "" + escapeHtml(name) + " " + escapeHtml(typeStr(type)) + "" + ""; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java index 0cc09c561..1be31cc5f 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -37,8 +37,8 @@ public class TextSearchIndex { private final JNodeCache nodeCache; private SearchIndex clsNamesIndex; - private SearchIndex mthNamesIndex; - private SearchIndex fldNamesIndex; + private SearchIndex mthSignaturesIndex; + private SearchIndex fldSignaturesIndex; private SearchIndex codeIndex; private List skippedClasses = new ArrayList<>(); @@ -46,18 +46,20 @@ public class TextSearchIndex { public TextSearchIndex(JNodeCache nodeCache) { this.nodeCache = nodeCache; this.clsNamesIndex = new SimpleIndex<>(); - this.mthNamesIndex = new SimpleIndex<>(); - this.fldNamesIndex = new SimpleIndex<>(); + this.mthSignaturesIndex = new SimpleIndex<>(); + this.fldSignaturesIndex = new SimpleIndex<>(); this.codeIndex = new CodeIndex<>(); } public void indexNames(JavaClass cls) { clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls)); for (JavaMethod mth : cls.getMethods()) { - mthNamesIndex.put(mth.getName(), nodeCache.makeFrom(mth)); + JNode mthNode = nodeCache.makeFrom(mth); + mthSignaturesIndex.put(mthNode.makeLongString(), mthNode); } for (JavaField fld : cls.getFields()) { - fldNamesIndex.put(fld.getName(), nodeCache.makeFrom(fld)); + JNode fldNode = nodeCache.makeFrom(fld); + fldSignaturesIndex.put(fldNode.makeLongString(), fldNode); } for (JavaClass innerCls : cls.getInnerClasses()) { indexNames(innerCls); @@ -97,10 +99,10 @@ public class TextSearchIndex { result = Flowable.concat(result, clsNamesIndex.search(text, ignoreCase)); } if (options.contains(METHOD)) { - result = Flowable.concat(result, mthNamesIndex.search(text, ignoreCase)); + result = Flowable.concat(result, mthSignaturesIndex.search(text, ignoreCase)); } if (options.contains(FIELD)) { - result = Flowable.concat(result, fldNamesIndex.search(text, ignoreCase)); + result = Flowable.concat(result, fldSignaturesIndex.search(text, ignoreCase)); } if (options.contains(CODE)) { if (codeIndex.size() > 0) { From 79ab2e11f8ad22b6e828276d50b826634ff943b9 Mon Sep 17 00:00:00 2001 From: Jan S Date: Sun, 15 Mar 2020 16:21:08 +0100 Subject: [PATCH 02/55] chore(gui): preferences dialog changed to two-column mode (PR #888) --- .../jadx/gui/settings/JadxSettingsWindow.java | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 3557c57d0..c28b0555d 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -1,6 +1,7 @@ package jadx.gui.settings; import java.awt.*; +import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -20,6 +21,7 @@ import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; public class JadxSettingsWindow extends JDialog { private static final long serialVersionUID = -1804570470377354148L; @@ -46,19 +48,29 @@ public class JadxSettingsWindow extends JDialog { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setModalityType(ModalityType.APPLICATION_MODAL); pack(); + UiUtils.setWindowIcons(this); setLocationRelativeTo(null); } private void initUI() { JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - panel.add(makeDeobfuscationGroup()); - panel.add(makeRenameGroup()); - panel.add(makeDecompilationGroup()); - panel.add(makeProjectGroup()); - panel.add(makeEditorGroup()); - panel.add(makeOtherGroup()); + + JPanel leftPanel = new JPanel(); + JPanel rightPanel = new JPanel(); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.PAGE_AXIS)); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.PAGE_AXIS)); + panel.add(leftPanel); + panel.add(rightPanel); + + leftPanel.add(makeDeobfuscationGroup()); + leftPanel.add(makeRenameGroup()); + leftPanel.add(makeProjectGroup()); + leftPanel.add(makeEditorGroup()); + leftPanel.add(makeOtherGroup()); + + rightPanel.add(makeDecompilationGroup()); JButton saveBtn = new JButton(NLS.str("preferences.save")); saveBtn.addActionListener(event -> { @@ -80,11 +92,7 @@ public class JadxSettingsWindow extends JDialog { }); }); JButton cancelButton = new JButton(NLS.str("preferences.cancel")); - cancelButton.addActionListener(event -> { - JadxSettingsAdapter.fill(settings, startSettings); - mainWindow.loadSettings(); - dispose(); - }); + cancelButton.addActionListener(event -> cancel()); JButton resetBtn = new JButton(NLS.str("preferences.reset")); resetBtn.addActionListener(event -> { @@ -107,7 +115,7 @@ public class JadxSettingsWindow extends JDialog { JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); - buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); buttonPane.add(resetBtn); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(saveBtn); @@ -120,6 +128,22 @@ public class JadxSettingsWindow extends JDialog { contentPane.add(scrollPane, BorderLayout.CENTER); contentPane.add(buttonPane, BorderLayout.PAGE_END); getRootPane().setDefaultButton(saveBtn); + + KeyStroke strokeEsc = KeyStroke.getKeyStroke("ESCAPE"); + InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + inputMap.put(strokeEsc, "ESCAPE"); + getRootPane().getActionMap().put("ESCAPE", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + cancel(); + } + }); + } + + private void cancel() { + JadxSettingsAdapter.fill(settings, startSettings); + mainWindow.loadSettings(); + dispose(); } private static void enableComponents(Container container, boolean enable) { From 2cc49256a9abe370e28382c8f557ee3c3f12ea15 Mon Sep 17 00:00:00 2001 From: Jan S Date: Sun, 15 Mar 2020 16:55:32 +0100 Subject: [PATCH 03/55] chore(gui): optimized the text search for classes, methods and fields (PR #887) --- .../src/main/java/jadx/gui/treemodel/JField.java | 10 ++++++++++ .../src/main/java/jadx/gui/treemodel/JMethod.java | 10 ++++++++++ jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java | 2 +- jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java | 12 +++++++++--- .../java/jadx/gui/utils/search/TextSearchIndex.java | 4 ++-- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java index 433ac6f20..8d84fc7ef 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java @@ -81,6 +81,16 @@ public class JField extends JNode { return UiUtils.typeFormatHtml(field.getFullName(), field.getType()); } + @Override + public String makeDescString() { + return UiUtils.typeStr(field.getType()) + " " + field.getName(); + } + + @Override + public boolean hasDescString() { + return true; + } + @Override public int hashCode() { return field.hashCode(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index ffe4e60d3..ec185b00f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -111,6 +111,16 @@ public class JMethod extends JNode { return UiUtils.typeFormatHtml(name, getReturnType()); } + @Override + public String makeDescString() { + return UiUtils.typeStr(getReturnType()) + " " + makeBaseString(); + } + + @Override + public boolean hasDescString() { + return true; + } + @Override public int hashCode() { return mth.hashCode(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 5d15a84fe..96b53ee37 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -94,7 +94,7 @@ public abstract class JNode extends DefaultMutableTreeNode { } public String makeLongStringHtml() { - return makeString(); + return makeLongString(); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index 5b62e6777..31194cbdb 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -1,6 +1,8 @@ package jadx.gui.utils; -import java.awt.*; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.Window; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; @@ -9,7 +11,11 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; -import javax.swing.*; +import javax.swing.Action; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.KeyStroke; import org.intellij.lang.annotations.MagicConstant; import org.slf4j.Logger; @@ -65,7 +71,7 @@ public class UiUtils { } public static String typeFormat(String name, ArgType type) { - return name + typeStr(type); + return name + " " + typeStr(type); } public static String typeFormatHtml(String name, ArgType type) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java index 1be31cc5f..8c8bce0b1 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -55,11 +55,11 @@ public class TextSearchIndex { clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls)); for (JavaMethod mth : cls.getMethods()) { JNode mthNode = nodeCache.makeFrom(mth); - mthSignaturesIndex.put(mthNode.makeLongString(), mthNode); + mthSignaturesIndex.put(mthNode.makeDescString(), mthNode); } for (JavaField fld : cls.getFields()) { JNode fldNode = nodeCache.makeFrom(fld); - fldSignaturesIndex.put(fldNode.makeLongString(), fldNode); + fldSignaturesIndex.put(fldNode.makeDescString(), fldNode); } for (JavaClass innerCls : cls.getInnerClasses()) { indexNames(innerCls); From 9d8066f4b80885fe973e31dc262d536bdbef1790 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 16 Mar 2020 19:06:52 +0000 Subject: [PATCH 04/55] fix: don't remove synthetic methods from enum (#884) --- .../src/main/java/jadx/core/dex/visitors/EnumVisitor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index dc653702f..290ee1815 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -303,9 +303,7 @@ public class EnumVisitor extends AbstractVisitor { || shortId.equals(enumConstructorAlt)) { mth.add(AFlag.DONT_GENERATE); } - } else if (isSynthetic - || shortId.equals(valuesMethod) - || shortId.equals(valuesOfMethod)) { + } else if (shortId.equals(valuesMethod) || shortId.equals(valuesOfMethod)) { mth.add(AFlag.DONT_GENERATE); } } From 2f780da305fdd2528c744d1188095152011f8084 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 16 Mar 2020 20:30:37 +0000 Subject: [PATCH 05/55] fix: remove enum methods after instructions check (#884) --- .../jadx/core/dex/visitors/EnumVisitor.java | 55 +++++++++++-- .../main/java/jadx/core/utils/InsnUtils.java | 72 +++++++++++++++++ .../integration/enums/TestEnumObfuscated.java | 49 +++++++++++ .../test/smali/enums/TestEnumObfuscated.smali | 81 +++++++++++++++++++ 4 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumObfuscated.java create mode 100644 jadx-core/src/test/smali/enums/TestEnumObfuscated.smali diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 290ee1815..edfe3fd4b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -1,10 +1,12 @@ package jadx.core.dex.visitors; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; @@ -23,6 +25,7 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -36,12 +39,17 @@ import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.BlockInsnPair; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxException; +import static jadx.core.utils.InsnUtils.checkInsnType; +import static jadx.core.utils.InsnUtils.getSingleArg; +import static jadx.core.utils.InsnUtils.getWrappedInsn; + @JadxVisitor( name = "EnumVisitor", desc = "Restore enum classes", @@ -50,6 +58,18 @@ import jadx.core.utils.exceptions.JadxException; ) public class EnumVisitor extends AbstractVisitor { + private MethodInfo enumValueOfMth; + + @Override + public void init(RootNode root) { + enumValueOfMth = MethodInfo.fromDetails( + root, + ClassInfo.fromType(root, ArgType.ENUM), + "valueOf", + Arrays.asList(ArgType.CLASS, ArgType.STRING), + ArgType.ENUM); + } + @Override public boolean visit(ClassNode cls) throws JadxException { if (!convertToEnum(cls)) { @@ -163,7 +183,7 @@ public class EnumVisitor extends AbstractVisitor { if (classInitMth.countInsns() == 0) { classInitMth.add(AFlag.DONT_GENERATE); } - removeEnumMethods(cls, clsType); + removeEnumMethods(cls, clsType, valuesField); return true; } @@ -232,7 +252,7 @@ public class EnumVisitor extends AbstractVisitor { if (ssaVar.getUseCount() == 1) { return null; } - final InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn(); + InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn(); if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) { return null; } @@ -283,14 +303,14 @@ public class EnumVisitor extends AbstractVisitor { return null; } - // TODO: detect these methods by analyzing method instructions - private void removeEnumMethods(ClassNode cls, ArgType clsType) { + private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) { String enumConstructor = "(Ljava/lang/String;I)V"; String enumConstructorAlt = "(Ljava/lang/String;)V"; - String valuesOfMethod = "valueOf(Ljava/lang/String;)" + TypeGen.signature(clsType); String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType)); - // remove synthetic methods + FieldInfo valuesFieldInfo = valuesField.getFieldInfo(); + + // remove compiler generated methods for (MethodNode mth : cls.getMethods()) { MethodInfo mi = mth.getMethodInfo(); if (mi.isClassInit()) { @@ -303,12 +323,33 @@ public class EnumVisitor extends AbstractVisitor { || shortId.equals(enumConstructorAlt)) { mth.add(AFlag.DONT_GENERATE); } - } else if (shortId.equals(valuesMethod) || shortId.equals(valuesOfMethod)) { + } else if (shortId.equals(valuesMethod) + || usesValuesField(mth, valuesFieldInfo) + || simpleValueOfMth(mth, clsType)) { mth.add(AFlag.DONT_GENERATE); } } } + private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) { + InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1); + if (returnInsn == null) { + return false; + } + InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn)); + IndexInsnNode castInsn = (IndexInsnNode) checkInsnType(wrappedInsn, InsnType.CHECK_CAST); + if (castInsn != null && Objects.equals(castInsn.getIndex(), clsType)) { + InvokeNode invokeInsn = (InvokeNode) checkInsnType(getWrappedInsn(getSingleArg(castInsn)), InsnType.INVOKE); + return invokeInsn != null && invokeInsn.getCallMth().equals(enumValueOfMth); + } + return false; + } + + private boolean usesValuesField(MethodNode mth, FieldInfo valuesFieldInfo) { + Predicate insnTest = insn -> Objects.equals(((IndexInsnNode) insn).getIndex(), valuesFieldInfo); + return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; + } + private static void processEnumInnerCls(ConstructorInsn co, EnumField field, ClassNode innerCls) { if (!innerCls.getClassInfo().equals(co.getClassType())) { return; 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 6b6afc190..0d9170644 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -1,5 +1,7 @@ package jadx.core.utils; +import java.util.function.Predicate; + import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,9 +17,11 @@ import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -121,4 +125,72 @@ public class InsnUtils { return null; } } + + @Nullable + public static InsnNode searchSingleReturnInsn(MethodNode mth, Predicate test) { + if (!mth.isNoCode() && mth.getExitBlocks().size() == 1) { + return searchInsn(mth, InsnType.RETURN, test); + } + return null; + } + + /** + * Search instruction of specific type and condition in method. + * This method support inlined instructions. + */ + @Nullable + public static InsnNode searchInsn(MethodNode mth, InsnType insnType, Predicate test) { + if (mth.isNoCode()) { + return null; + } + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + InsnNode foundInsn = recursiveInsnCheck(insn, insnType, test); + if (foundInsn != null) { + return foundInsn; + } + } + } + return null; + } + + private static InsnNode recursiveInsnCheck(InsnNode insn, InsnType insnType, Predicate test) { + if (insn.getType() == insnType && test.test(insn)) { + return insn; + } + for (InsnArg arg : insn.getArguments()) { + if (arg.isInsnWrap()) { + InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); + InsnNode foundInsn = recursiveInsnCheck(wrapInsn, insnType, test); + if (foundInsn != null) { + return foundInsn; + } + } + } + return null; + } + + @Nullable + public static InsnArg getSingleArg(InsnNode insn) { + if (insn != null && insn.getArgsCount() == 1) { + return insn.getArg(0); + } + return null; + } + + @Nullable + public static InsnNode checkInsnType(InsnNode insn, InsnType insnType) { + if (insn != null && insn.getType() == insnType) { + return insn; + } + return null; + } + + @Nullable + public static InsnNode getWrappedInsn(InsnArg arg) { + if (arg != null && arg.isInsnWrap()) { + return ((InsnWrapArg) arg).getWrapInsn(); + } + return null; + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumObfuscated.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumObfuscated.java new file mode 100644 index 000000000..f4e789e42 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumObfuscated.java @@ -0,0 +1,49 @@ +package jadx.tests.integration.enums; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestEnumObfuscated extends SmaliTest { + // @formatter:off + /* + public enum TestEnumObfuscated { + private static final synthetic TestEnumObfuscated[] $VLS = {ONE, TWO}; + public static final TestEnumObfuscated ONE = new TestEnumObfuscated("ONE", 0, 1); + public static final TestEnumObfuscated TWO = new TestEnumObfuscated("TWO", 1, 2); + private final int num; + + private TestEnumObfuscated(String str, int i, int i2) { + super(str, i); + this.num = i2; + } + + public static TestEnumObfuscated vo(String str) { + return (TestEnumObfuscated) Enum.valueOf(TestEnumObfuscated.class, str); + } + + public static TestEnumObfuscated[] vs() { + return (TestEnumObfuscated[]) $VLS.clone(); + } + + public synthetic int getNum() { + return this.num; + } + } + */ + // @formatter:on + + @Test + public void test() { + ClassNode cls = getClassNodeFromSmali(); + assertThat(cls) + .code() + .doesNotContain("$VLS") + .doesNotContain("vo(") + .doesNotContain("vs(") + .containsOne("int getNum() {"); + } +} diff --git a/jadx-core/src/test/smali/enums/TestEnumObfuscated.smali b/jadx-core/src/test/smali/enums/TestEnumObfuscated.smali new file mode 100644 index 000000000..99e91e69c --- /dev/null +++ b/jadx-core/src/test/smali/enums/TestEnumObfuscated.smali @@ -0,0 +1,81 @@ +###### Class jadx.tests.integration.enums.TestEnums3$TestCls.Numbers (jadx.tests.integration.enums.TestEnums3$TestCls$Numbers) +.class public final enum Lenums/TestEnumObfuscated; +.super Ljava/lang/Enum; + +# static fields +.field private static final synthetic $VLS:[Lenums/TestEnumObfuscated; +.field public static final enum ONE:Lenums/TestEnumObfuscated; +.field public static final enum TWO:Lenums/TestEnumObfuscated; + +# instance fields +.field private final num:I + +# direct methods +.method static constructor ()V + .registers 7 + + .prologue + const/4 v6, 0x3 + const/4 v5, 0x0 + const/4 v4, 0x2 + const/4 v3, 0x1 + + new-instance v0, Lenums/TestEnumObfuscated; + const-string v1, "ONE" + invoke-direct {v0, v1, v5, v3}, Lenums/TestEnumObfuscated;->(Ljava/lang/String;II)V + sput-object v0, Lenums/TestEnumObfuscated;->ONE:Lenums/TestEnumObfuscated; + new-instance v0, Lenums/TestEnumObfuscated; + + const-string v1, "TWO" + invoke-direct {v0, v1, v3, v4}, Lenums/TestEnumObfuscated;->(Ljava/lang/String;II)V + sput-object v0, Lenums/TestEnumObfuscated;->TWO:Lenums/TestEnumObfuscated; + const/4 v0, 0x2 + + new-array v0, v0, [Lenums/TestEnumObfuscated; + sget-object v1, Lenums/TestEnumObfuscated;->ONE:Lenums/TestEnumObfuscated; + aput-object v1, v0, v5 + sget-object v1, Lenums/TestEnumObfuscated;->TWO:Lenums/TestEnumObfuscated; + aput-object v1, v0, v3 + sput-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated; + + return-void +.end method + +.method private constructor (Ljava/lang/String;II)V + .registers 4 + + .prologue + invoke-direct {p0, p1, p2}, Ljava/lang/Enum;->(Ljava/lang/String;I)V + iput p3, p0, Lenums/TestEnumObfuscated;->num:I + return-void +.end method + +.method public static vo(Ljava/lang/String;)Lenums/TestEnumObfuscated; + .registers 2 + + .prologue + const-class v0, Lenums/TestEnumObfuscated; + invoke-static {v0, p0}, Ljava/lang/Enum;->valueOf(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; + move-result-object v0 + check-cast v0, Lenums/TestEnumObfuscated; + return-object v0 +.end method + +.method public static vs()[Lenums/TestEnumObfuscated; + .registers 1 + + .prologue + sget-object v0, Lenums/TestEnumObfuscated;->$VLS:[Lenums/TestEnumObfuscated; + invoke-virtual {v0}, [Lenums/TestEnumObfuscated;->clone()Ljava/lang/Object; + move-result-object v0 + check-cast v0, [Lenums/TestEnumObfuscated; + return-object v0 +.end method + +.method public synthetic getNum()I + .registers 2 + + .prologue + iget v0, p0, Lenums/TestEnumObfuscated;->num:I + return v0 +.end method From 4cdad0e83e0369a82b1e9ed169a5a5d057bcf086 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 17 Mar 2020 19:38:45 +0000 Subject: [PATCH 06/55] fix: correct method exit blocks collection (#876) --- .../visitors/blocksmaker/BlockProcessor.java | 33 +++++++++++--- .../switches/TestSwitchWithThrow.java | 44 +++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchWithThrow.java diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index 4e703458e..64fca3ce4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -58,7 +58,7 @@ public class BlockProcessor extends AbstractVisitor { clearBlocksState(mth); computeDominators(mth); } - markReturnBlocks(mth); + updateExitBlocks(mth); int i = 0; while (modifyBlocksTree(mth)) { @@ -66,7 +66,7 @@ public class BlockProcessor extends AbstractVisitor { clearBlocksState(mth); // recalculate dominators tree computeDominators(mth); - markReturnBlocks(mth); + updateExitBlocks(mth); if (i++ > 100) { mth.addWarn("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size()); @@ -337,12 +337,33 @@ public class BlockProcessor extends AbstractVisitor { block.setDomFrontier(domFrontier); } - private static void markReturnBlocks(MethodNode mth) { + private static void updateExitBlocks(MethodNode mth) { mth.getExitBlocks().clear(); mth.getBasicBlocks().forEach(block -> { - if (BlockUtils.checkLastInsnType(block, InsnType.RETURN)) { - block.add(AFlag.RETURN); - mth.getExitBlocks().add(block); + boolean noSuccessors = block.getSuccessors().isEmpty(); + boolean exitBlock = false; + InsnNode lastInsn = BlockUtils.getLastInsn(block); + if (lastInsn != null) { + InsnType insnType = lastInsn.getType(); + if (insnType == InsnType.RETURN) { + block.add(AFlag.RETURN); + exitBlock = true; + if (!noSuccessors) { + throw new JadxRuntimeException("Found a block after RETURN instruction: " + lastInsn + " in block: " + block); + } + } else if (insnType == InsnType.THROW) { + if (noSuccessors) { + exitBlock = true; + } + } + } + if (exitBlock) { + mth.addExitBlock(block); + } else if (noSuccessors + && !mth.isVoidReturn() + && !mth.isConstructor()) { + mth.addComment("JADX INFO: Unexpected exit block: " + block + + ". Expect last instruction to be RETURN or THROW, got: " + lastInsn); } }); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchWithThrow.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchWithThrow.java new file mode 100644 index 000000000..d2b65db2a --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchWithThrow.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.switches; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestSwitchWithThrow extends IntegrationTest { + + public static class TestCls { + public int test(int i) { + if (i != 0) { + switch (i % 4) { + case 1: + throw new IllegalStateException("1"); + case 2: + throw new IllegalStateException("2"); + default: + throw new IllegalStateException("Other"); + } + } else { + System.out.println("0"); + return -1; + } + } + + public void check() { + assertThat(test(0)).isEqualTo(-1); + // TODO: implement 'invoke-custom' support + // assertThat(catchThrowable(() -> test(1))) + // .isInstanceOf(IllegalStateException.class).hasMessageContaining("1"); + // assertThat(catchThrowable(() -> test(3))) + // .isInstanceOf(IllegalStateException.class).hasMessageContaining("Other"); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .contains("throw new IllegalStateException(\"1\");"); + } +} From 2da772df8ec7d5e1a69b36d7a5bb2b0da41bc917 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 21 Mar 2020 18:41:54 +0000 Subject: [PATCH 07/55] fix: resolve some cases of switch in loop (#876) --- .../core/dex/instructions/SwitchNode.java | 25 +++++++++----- .../dex/visitors/regions/RegionMaker.java | 28 +++++++++++---- .../java/jadx/core/utils/ErrorsCounter.java | 5 +++ .../switches/TestSwitchInLoop2.java | 34 +++++++++++++++++++ 4 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchInLoop2.java diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java index ed4f79076..374ddd412 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java @@ -119,14 +119,23 @@ public class SwitchNode extends TargetInsnNode { public String toString() { StringBuilder sb = new StringBuilder(); sb.append(super.toString()); - for (int i = 0; i < targets.length; i++) { - sb.append(CodeWriter.NL); - sb.append(" case ").append(keys[i]); - sb.append(": goto ").append(InsnUtils.formatOffset(targets[i])); - } - if (def != -1) { - sb.append(CodeWriter.NL); - sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); + if (targetBlocks == null) { + for (int i = 0; i < keys.length; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i])); + } + if (def != -1) { + sb.append(CodeWriter.NL); + sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); + } + } else { + for (int i = 0; i < keys.length; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]); + } + if (def != -1) { + sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock); + } } return sb.toString(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index 832b8123a..da956201e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -761,14 +761,30 @@ public class RegionMaker { out = calcPostDomOut(mth, block, mth.getExitBlocks()); } else { BlockNode loopEnd = loop.getEnd(); - // treat 'continue' as exit - out = calcPostDomOut(mth, block, loopEnd.getPredecessors()); - if (out != null) { - insertContinueInSwitch(block, out, loopEnd); + stack.addExit(loop.getStart()); + if (stack.containsExit(block) + || block == loopEnd + || loopEnd.getPredecessors().contains(block)) { + // in exits or last insn in loop => no 'out' block + out = null; } else { - // no 'continue' - out = calcPostDomOut(mth, block, Collections.singletonList(loopEnd)); + // treat 'continue' as exit + out = calcPostDomOut(mth, block, loopEnd.getPredecessors()); + if (out != null) { + insertContinueInSwitch(block, out, loopEnd); + } else { + // no 'continue' + out = calcPostDomOut(mth, block, Collections.singletonList(loopEnd)); + } } + if (out == loop.getStart()) { + // no other outs instead back edge to loop start + out = null; + } + } + if (out != null && processedBlocks.get(out.getId())) { + // out block already processed, prevent endless loop + throw new JadxRuntimeException("Failed to find switch 'out' block"); } SwitchRegion sw = new SwitchRegion(currentRegion, block); diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java index fc88a662e..304c38267 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java +++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java @@ -21,6 +21,7 @@ import jadx.core.utils.exceptions.JadxOverflowException; public class ErrorsCounter { private static final Logger LOG = LoggerFactory.getLogger(ErrorsCounter.class); + private static final boolean PRINT_MTH_SIZE = true; private final Set errorNodes = new HashSet<>(); private int errorsCount; @@ -40,6 +41,10 @@ public class ErrorsCounter { errorsCount++; String msg = formatMsg(node, error); + if (PRINT_MTH_SIZE && node instanceof MethodNode) { + long insnsCount = ((MethodNode) node).countInsns(); + msg = "[" + insnsCount + "] " + msg; + } if (e == null) { LOG.error(msg); } else if (e instanceof JadxOverflowException) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchInLoop2.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchInLoop2.java new file mode 100644 index 000000000..e0fa866c2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchInLoop2.java @@ -0,0 +1,34 @@ +package jadx.tests.integration.switches; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestSwitchInLoop2 extends IntegrationTest { + public static class TestCls { + public boolean test() { + while (true) { + switch (call()) { + case 0: + return false; + case 1: + return true; + } + } + } + + private int call() { + return 0; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("while (true) {") + .containsOne("switch (call()) {"); + } +} From dbd00d5a8b2a2d02138009012c9360280e2c4448 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 22 Mar 2020 12:00:36 +0000 Subject: [PATCH 08/55] refactor: use instance methods for error and warning notifications --- .../src/main/java/jadx/core/ProcessClass.java | 3 +- .../main/java/jadx/core/codegen/ClassGen.java | 6 +- .../java/jadx/core/codegen/ConditionGen.java | 3 +- .../java/jadx/core/codegen/RegionGen.java | 3 +- .../nodes/NotificationAttrNode.java | 40 +++++++++++++ .../java/jadx/core/dex/info/ConstStorage.java | 3 +- .../java/jadx/core/dex/nodes/ClassNode.java | 4 +- .../java/jadx/core/dex/nodes/DexNode.java | 2 +- .../java/jadx/core/dex/nodes/MethodNode.java | 36 +---------- .../core/dex/visitors/DepthTraversal.java | 16 ++--- .../jadx/core/dex/visitors/ModVisitor.java | 6 +- .../regions/ProcessTryCatchRegions.java | 3 +- .../dex/visitors/regions/RegionMaker.java | 2 +- .../java/jadx/core/utils/ErrorsCounter.java | 60 ++++++++++--------- .../src/main/java/jadx/core/utils/Utils.java | 5 +- 15 files changed, 98 insertions(+), 94 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 94eb12006..35e82ebc3 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -7,7 +7,6 @@ import jadx.core.codegen.CodeGen; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; @@ -43,7 +42,7 @@ public final class ProcessClass { cls.setState(PROCESS_COMPLETE); } } catch (Throwable e) { - ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e); + cls.addError("Class process error: " + e.getClass().getSimpleName(), e); } } } 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 a35b7e7ef..5044fa649 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -268,7 +268,7 @@ public class ClassGen { inClGen.addClassCode(code); imports.addAll(inClGen.getImports()); } catch (Exception e) { - ErrorsCounter.classError(innerCls, "Inner class code generation error", e); + innerCls.addError("Inner class code generation error", e); } } @@ -293,7 +293,7 @@ public class ClassGen { throw new JadxRuntimeException("Method generation error", e); } code.newLine().add("/*"); - code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e)); + code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e)); Utils.appendStackTrace(code, e); code.newLine().add("*/"); code.setIndent(savedIndent); @@ -455,7 +455,7 @@ public class ClassGen { try { insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP); } catch (Exception e) { - ErrorsCounter.classError(cls, "Failed to generate init code", e); + cls.addError("Failed to generate init code", e); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java b/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java index f13f72152..ee3ed1fb3 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ConditionGen.java @@ -16,7 +16,6 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.regions.conditions.Compare; import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition.Mode; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -123,7 +122,7 @@ public class ConditionGen extends InsnGen { wrap(code, firstArg); return; } - ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol()); + mth.addWarn("Unsupported boolean condition " + op.getSymbol()); } addArg(code, firstArg, isArgWrapNeeded(firstArg)); 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 59069d679..eef789687 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -39,7 +39,6 @@ import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.regions.loops.LoopType; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.utils.BlockUtils; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.RegionUtils; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -186,7 +185,7 @@ public class RegionGen extends InsnGen { if (header != null) { List headerInsns = header.getInstructions(); if (headerInsns.size() > 1) { - ErrorsCounter.methodWarn(mth, "Found not inlined instructions from loop header"); + mth.addWarn("Found not inlined instructions from loop header"); int last = headerInsns.size() - 1; for (int i = 0; i < last; i++) { InsnNode insn = headerInsns.get(i); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java new file mode 100644 index 000000000..c2f72087b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java @@ -0,0 +1,40 @@ +package jadx.core.dex.attributes.nodes; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.nodes.ICodeNode; +import jadx.core.utils.ErrorsCounter; + +public abstract class NotificationAttrNode extends LineAttrNode implements ICodeNode { + private static final Logger LOG = LoggerFactory.getLogger(NotificationAttrNode.class); + + public void addError(String errStr, Throwable e) { + ErrorsCounter.error(this, errStr, e); + } + + public void addWarn(String warnStr) { + ErrorsCounter.warning(this, warnStr); + } + + public void addWarnComment(String warn) { + addWarnComment(warn, null); + } + + public void addWarnComment(String warn, @Nullable Throwable exc) { + String commentStr = "JADX WARN: " + warn; + addAttr(AType.COMMENTS, commentStr); + if (exc != null) { + LOG.warn("{} in {}", warn, this, exc); + } else { + LOG.warn("{} in {}", warn, this); + } + } + + public void addComment(String commentStr) { + addAttr(AType.COMMENTS, commentStr); + LOG.info("{} in {}", commentStr, this); + } +} 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 4ecf70271..d71095fdd 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 @@ -16,7 +16,6 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.utils.ErrorsCounter; public class ConstStorage { @@ -155,7 +154,7 @@ public class ConstStorage { return innerClass.searchFieldByName(fieldName); } } - ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.')); + appResClass.addWarn("Not found resource field with id: " + value + ", name: " + str.replace('/', '.')); return null; } 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 8d3867083..39ff60911 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 @@ -25,7 +25,7 @@ import jadx.core.Consts; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.annotations.Annotation; -import jadx.core.dex.attributes.nodes.LineAttrNode; +import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; @@ -45,7 +45,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; -public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { +public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final DexNode dex; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java index f76578df9..febb7a2ae 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java @@ -79,7 +79,7 @@ public class DexNode implements IDexNode { name = "CLASS_" + typeIndex; } ClassNode clsNode = new ClassNode(this, name, classDef.getAccessFlags()); - ErrorsCounter.classError(clsNode, "Load error", exc); + ErrorsCounter.error(clsNode, "Load error", exc); addClassNode(clsNode); } 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 fd2aee0fd..7e30b07af 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 @@ -22,8 +22,8 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.nodes.JumpInfo; -import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LoopInfo; +import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.ClassInfo; @@ -42,14 +42,13 @@ import jadx.core.dex.regions.Region; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.TryCatchBlock; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.utils.Utils.lockList; -public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadable, ICodeNode { +public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class); private final MethodInfo mthInfo; @@ -203,9 +202,7 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl return null; } if (!tryFixArgsCounts(argsTypes, mthArgs)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Incorrect method signature, types: ({}), method: {}", Utils.listToString(argsTypes), this); - } + addComment("Incorrect method signature, types: " + Utils.listToString(argsTypes)); return null; } } @@ -715,33 +712,6 @@ public class MethodNode extends LineAttrNode implements IMethodDetails, ILoadabl return "method"; } - public void addWarn(String warnStr) { - ErrorsCounter.methodWarn(this, warnStr); - } - - public void addWarnComment(String warn) { - addWarnComment(warn, null); - } - - public void addWarnComment(String warn, @Nullable Throwable exc) { - String commentStr = "JADX WARN: " + warn; - addAttr(AType.COMMENTS, commentStr); - if (exc != null) { - LOG.warn("{} in {}", warn, this, exc); - } else { - LOG.warn("{} in {}", warn, this); - } - } - - public void addComment(String commentStr) { - addAttr(AType.COMMENTS, commentStr); - LOG.info("{} in {}", commentStr, this); - } - - public void addError(String errStr, Throwable e) { - ErrorsCounter.methodError(this, errStr, e); - } - @Override public MethodInfo getMethodInfo() { return mthInfo; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java index dfe8445c8..a8ef3ca78 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java @@ -4,8 +4,6 @@ import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.DebugChecks; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.exceptions.JadxOverflowException; public class DepthTraversal { @@ -15,11 +13,8 @@ public class DepthTraversal { cls.getInnerClasses().forEach(inCls -> visit(visitor, inCls)); cls.getMethods().forEach(mth -> visit(visitor, mth)); } - } catch (StackOverflowError e) { - ErrorsCounter.classError(cls, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException("")); - } catch (Exception e) { - ErrorsCounter.classError(cls, - e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e); + } catch (StackOverflowError | Exception e) { + cls.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e); } } @@ -32,11 +27,8 @@ public class DepthTraversal { if (DebugChecks.checksEnabled) { DebugChecks.runChecksAfterVisitor(mth, visitor); } - } catch (StackOverflowError e) { - ErrorsCounter.methodError(mth, "StackOverflow in pass: " + visitor.getClass().getSimpleName(), new JadxOverflowException("")); - } catch (Exception e) { - ErrorsCounter.methodError(mth, - e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e); + } catch (StackOverflowError | Exception e) { + mth.addError(e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e); } } 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 a8b57758c..03b76533f 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 @@ -46,7 +46,6 @@ import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxException; @@ -467,9 +466,8 @@ public class ModVisitor extends AbstractVisitor { elType = insnElementType; } if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) { - ErrorsCounter.methodWarn(mth, - "Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset()) - + ", element type: " + elType + ", insn element type: " + insnElementType); + mth.addWarn("Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset()) + + ", element type: " + elType + ", insn element type: " + insnElementType); } if (!elType.isTypeKnown()) { LOG.warn("Unknown array element type: {} in mth: {}", elType, mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index a00b1f81f..642ca5f5f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -23,7 +23,6 @@ import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.SplitterBlockAttr; import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.BlockUtils; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.RegionUtils; /** @@ -105,7 +104,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { if (region.getSubBlocks().contains(dominator)) { TryCatchBlock tb = tryBlocksMap.get(dominator); if (!wrapBlocks(region, tb, dominator)) { - ErrorsCounter.methodWarn(mth, "Can't wrap try/catch for region: " + region); + mth.addWarn("Can't wrap try/catch for region: " + region); } tryBlocksMap.remove(dominator); return true; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index da956201e..d538ce714 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -574,7 +574,7 @@ public class RegionMaker { BlockNode body = getNextBlock(block); if (body == null) { - ErrorsCounter.methodWarn(mth, "Unexpected end of synchronized block"); + mth.addWarn("Unexpected end of synchronized block"); return null; } BlockNode exit = null; diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java index 304c38267..c886a42ef 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java +++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java @@ -14,7 +14,6 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.attributes.nodes.JadxError; -import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.IDexNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.exceptions.JadxOverflowException; @@ -28,12 +27,16 @@ public class ErrorsCounter { private final Set warnNodes = new HashSet<>(); private int warnsCount; - public int getErrorCount() { - return errorsCount; + public static String error(N node, String warnMsg, Throwable th) { + return node.root().getErrorsCounter().addError(node, warnMsg, th); } - public int getWarnsCount() { - return warnsCount; + public static String warning(N node, String warnMsg) { + return node.root().getErrorsCounter().addWarning(node, warnMsg); + } + + public static String formatMsg(IDexNode node, String msg) { + return msg + " in " + node.typeName() + ": " + node + ", dex: " + node.dex().getDexFile().getName(); } private synchronized String addError(N node, String error, @Nullable Throwable e) { @@ -47,10 +50,17 @@ public class ErrorsCounter { } if (e == null) { LOG.error(msg); + } else if (e instanceof StackOverflowError) { + LOG.error(msg); } else if (e instanceof JadxOverflowException) { // don't print full stack trace - e = new JadxOverflowException(e.getMessage()); - LOG.error("{}, details: {}", msg, e.getMessage()); + String details = e.getMessage(); + e = new JadxOverflowException(details); + if (details == null || details.isEmpty()) { + LOG.error("{}", msg); + } else { + LOG.error("{}, details: {}", msg, details); + } } else { LOG.error(msg, e); } @@ -74,26 +84,6 @@ public class ErrorsCounter { return msg; } - public static String classError(ClassNode cls, String errorMsg, Throwable e) { - return cls.dex().root().getErrorsCounter().addError(cls, errorMsg, e); - } - - public static String classWarn(ClassNode cls, String warnMsg) { - return cls.dex().root().getErrorsCounter().addWarning(cls, warnMsg); - } - - public static String methodError(MethodNode mth, String errorMsg, Throwable e) { - return mth.root().getErrorsCounter().addError(mth, errorMsg, e); - } - - public static String methodWarn(MethodNode mth, String warnMsg) { - return mth.root().getErrorsCounter().addWarning(mth, warnMsg); - } - - public static String formatMsg(IDexNode node, String msg) { - return msg + " in " + node.typeName() + ": " + node + ", dex: " + node.dex().getDexFile().getName(); - } - public void printReport() { if (getErrorCount() > 0) { LOG.error("{} errors occurred in following nodes:", getErrorCount()); @@ -111,4 +101,20 @@ public class ErrorsCounter { LOG.warn("{} warnings in {} nodes", getWarnsCount(), warnNodes.size()); } } + + public int getErrorCount() { + return errorsCount; + } + + public int getWarnsCount() { + return warnsCount; + } + + public Set getErrorNodes() { + return errorNodes; + } + + public Set getWarnNodes() { + return warnNodes; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index daf73e7e8..341517f7f 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -158,14 +158,17 @@ public class Utils { private static void filter(Throwable th) { StackTraceElement[] stackTrace = th.getStackTrace(); int length = stackTrace.length; + StackTraceElement prevElement = null; for (int i = 0; i < length; i++) { StackTraceElement stackTraceElement = stackTrace[i]; String clsName = stackTraceElement.getClassName(); if (clsName.equals(STACKTRACE_STOP_CLS_NAME) - || clsName.startsWith(JADX_API_PACKAGE)) { + || clsName.startsWith(JADX_API_PACKAGE) + || Objects.equals(prevElement, stackTraceElement)) { th.setStackTrace(Arrays.copyOfRange(stackTrace, 0, i)); return; } + prevElement = stackTraceElement; } } From e09e933f9ca719c05d647367198878ddc70f02ac Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 22 Mar 2020 12:11:58 +0000 Subject: [PATCH 09/55] fix: additional checks for class signature --- .../java/jadx/core/dex/nodes/ClassNode.java | 19 ++- .../dex/nodes/parser/SignatureParser.java | 15 ++- .../generics/TestClassSignature.java | 23 ++++ .../smali/generics/TestClassSignature.smali | 115 ++++++++++++++++++ 4 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestClassSignature.java create mode 100644 jadx-core/src/test/smali/generics/TestClassSignature.smali 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 39ff60911..30629806b 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 @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.jetbrains.annotations.Nullable; @@ -194,6 +195,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN root().getConstValues().processConstFields(this, staticFields); } + /** + * Class signature format: + * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1 + */ private void parseClassSignature() { SignatureParser sp = SignatureParser.fromNode(this); if (sp == null) { @@ -203,7 +208,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN // parse class generic map generics = sp.consumeGenericTypeParameters(); // parse super class signature - superClass = sp.consumeType(); + superClass = validateSuperCls(sp.consumeType(), superClass); // parse interfaces signatures for (int i = 0; i < interfaces.size(); i++) { ArgType type = sp.consumeType(); @@ -218,6 +223,18 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } } + private ArgType validateSuperCls(ArgType candidateType, ArgType currentType) { + if (!candidateType.isObject()) { + this.addComment("Incorrect class signature, super class is not object: " + SignatureParser.getSignature(this)); + return currentType; + } + if (Objects.equals(candidateType.getObject(), this.getClassInfo().getType().getObject())) { + this.addComment("Incorrect class signature, super class is equals to this class: " + SignatureParser.getSignature(this)); + return currentType; + } + return candidateType; + } + private void setFieldsTypesFromSignature() { for (FieldNode field : fields) { try { 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 4b70dfd7c..f4adb4dab 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 @@ -33,14 +33,23 @@ public class SignatureParser { mark = 0; } - @SuppressWarnings("unchecked") + @Nullable public static SignatureParser fromNode(IAttributeNode node) { + String signature = getSignature(node); + if (signature == null) { + return null; + } + return new SignatureParser(signature); + } + + @SuppressWarnings("unchecked") + @Nullable + public static String getSignature(IAttributeNode node) { Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE); if (a == null) { return null; } - String signature = mergeSignature((List) a.getDefaultValue()); - return new SignatureParser(signature); + return mergeSignature((List) a.getDefaultValue()); } private char next() { diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestClassSignature.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestClassSignature.java new file mode 100644 index 000000000..d9a51126c --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestClassSignature.java @@ -0,0 +1,23 @@ +package jadx.tests.integration.generics; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestClassSignature extends SmaliTest { + // @formatter:off + /* + Incorrect class signature, super class is equals to this class: Lgenerics/TestClassSignature; + */ + // @formatter:on + + @Test + public void test() { + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("Incorrect class signature") + .doesNotContain("StackOverflowError"); + } +} diff --git a/jadx-core/src/test/smali/generics/TestClassSignature.smali b/jadx-core/src/test/smali/generics/TestClassSignature.smali new file mode 100644 index 000000000..e95aa72ec --- /dev/null +++ b/jadx-core/src/test/smali/generics/TestClassSignature.smali @@ -0,0 +1,115 @@ +.class public abstract Lgenerics/TestClassSignature; +.super Ljava/lang/Object; +.source "SourceFile" + +# interfaces +.implements Ljava/util/Iterator; + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "Lgenerics/TestClassSignature<", + "TT;>;" + } +.end annotation + + +# instance fields +.field public f:Ljava/lang/Object; + .annotation system Ldalvik/annotation/Signature; + value = { + "TT;" + } + .end annotation +.end field + + +# direct methods +.method public constructor (Ljava/lang/Object;)V + .registers 2 + .annotation system Ldalvik/annotation/Signature; + value = { + "(TT;)V" + } + .end annotation + + invoke-direct {p0}, Ljava/lang/Object;->()V + iput-object p1, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + return-void +.end method + + +# virtual methods +.method public abstract a(Ljava/lang/Object;)Ljava/lang/Object; + .annotation system Ldalvik/annotation/Signature; + value = { + "(TT;)TT;" + } + .end annotation +.end method + +.method public final hasNext()Z + .registers 2 + + .line 1 + iget-object v0, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + if-eqz v0, :cond_6 + const/4 v0, 0x1 + goto :goto_7 + + :cond_6 + const/4 v0, 0x0 + + :goto_7 + return v0 +.end method + +.method public final next()Ljava/lang/Object; + .registers 3 + .annotation system Ldalvik/annotation/Signature; + value = { + "()TT;" + } + .end annotation + + invoke-virtual {p0}, Lgenerics/TestClassSignature;->hasNext()Z + move-result v0 + if-eqz v0, :cond_1b + + :try_start_6 + iget-object v0, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + :try_end_8 + .catchall {:try_start_6 .. :try_end_8} :catchall_11 + + iget-object v1, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + invoke-virtual {p0, v1}, Lgenerics/TestClassSignature;->a(Ljava/lang/Object;)Ljava/lang/Object; + move-result-object v1 + iput-object v1, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + return-object v0 + + :catchall_11 + move-exception v0 + + iget-object v1, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + invoke-virtual {p0, v1}, Lgenerics/TestClassSignature;->a(Ljava/lang/Object;)Ljava/lang/Object; + move-result-object v1 + iput-object v1, p0, Lgenerics/TestClassSignature;->f:Ljava/lang/Object; + throw v0 + + :cond_1b + new-instance v0, Ljava/util/NoSuchElementException; + invoke-direct {v0}, Ljava/util/NoSuchElementException;->()V + throw v0 +.end method + +.method public final remove()V + .registers 2 + + new-instance v0, Ljava/lang/UnsupportedOperationException; + invoke-direct {v0}, Ljava/lang/UnsupportedOperationException;->()V + throw v0 +.end method From a5ea560edc72c81b574ea2d83c473c6c53353780 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 30 Mar 2020 17:21:57 +0100 Subject: [PATCH 10/55] fix: preserve code semantics on array-for-each transform (#893) --- .../main/java/jadx/core/codegen/InsnGen.java | 3 + .../java/jadx/core/codegen/RegionGen.java | 12 --- .../core/dex/attributes/nodes/LoopInfo.java | 4 + .../dex/instructions/ConstStringNode.java | 3 +- .../jadx/core/dex/instructions/InsnType.java | 3 + .../dex/instructions/args/LiteralArg.java | 5 +- .../java/jadx/core/dex/nodes/InsnNode.java | 9 +++ .../core/dex/regions/loops/ForEachLoop.java | 29 ++++++-- .../visitors/blocksmaker/BlockProcessor.java | 35 ++++++++- .../visitors/regions/LoopRegionVisitor.java | 35 ++++----- .../java/jadx/core/utils/InsnRemover.java | 2 + .../main/java/jadx/core/utils/InsnUtils.java | 18 +++++ .../java/jadx/core/utils/StringUtils.java | 5 ++ .../integration/loops/TestBreakWithLabel.java | 2 +- .../integration/loops/TestLoopRestore.java | 19 +++++ .../integration/types/TestGenerics2.java | 14 ++-- .../test/smali/loops/TestLoopRestore.smali | 74 +++++++++++++++++++ 17 files changed, 221 insertions(+), 51 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java create mode 100644 jadx-core/src/test/smali/loops/TestLoopRestore.smali 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 e6830d4e8..1238d1a75 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -225,6 +225,9 @@ public class InsnGen { private static final Set BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP); protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException { + if (insn.getType() == InsnType.REGION_ARG) { + return; + } try { if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) { makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS); 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 eef789687..5fdd65e57 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -181,18 +181,6 @@ public class RegionGen extends InsnGen { } private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException { - BlockNode header = region.getHeader(); - if (header != null) { - List headerInsns = header.getInstructions(); - if (headerInsns.size() > 1) { - mth.addWarn("Found not inlined instructions from loop header"); - int last = headerInsns.size() - 1; - for (int i = 0; i < last; i++) { - InsnNode insn = headerInsns.get(i); - makeInsn(insn, code); - } - } - } LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL); if (labelAttr != null) { code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':'); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java index 106b15c8b..35935153e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java @@ -72,6 +72,10 @@ public class LoopInfo { return edges; } + public BlockNode getPreHeader() { + return BlockUtils.selectOther(end, start.getPredecessors()); + } + public int getId() { return id; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ConstStringNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ConstStringNode.java index f6ad50fb0..f80a3f70d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ConstStringNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ConstStringNode.java @@ -1,6 +1,7 @@ package jadx.core.dex.instructions; import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.StringUtils; public final class ConstStringNode extends InsnNode { @@ -34,6 +35,6 @@ public final class ConstStringNode extends InsnNode { @Override public String toString() { - return super.toString() + " \"" + str + '"'; + return super.toString() + ' ' + StringUtils.getInstance().unescapeString(str); } } 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 9708f802f..eb184a591 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 @@ -66,6 +66,9 @@ public enum InsnType { ONE_ARG, PHI, + // fake insn to keep arguments which will be used in regions codegen + REGION_ARG, + // TODO: now multidimensional arrays created using Array.newInstance function NEW_MULTIDIM_ARRAY } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java index b0a6c5baa..0c284fafb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java @@ -1,6 +1,5 @@ package jadx.core.dex.instructions.args; -import jadx.api.JadxArgs; import jadx.core.codegen.TypeGen; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -72,12 +71,10 @@ public final class LiteralArg extends InsnArg { return literal == that.literal && getType().equals(that.getType()); } - private static final StringUtils DEF_STRING_UTILS = new StringUtils(new JadxArgs()); - @Override public String toString() { try { - String value = TypeGen.literalToString(literal, getType(), DEF_STRING_UTILS, true, false); + String value = TypeGen.literalToString(literal, getType(), StringUtils.getInstance(), true, false); if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) { return value; } 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 58012dc4b..71aff0ad4 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 @@ -272,6 +272,15 @@ public class InsnNode extends LineAttrNode { return true; } + public boolean containsWrappedInsn() { + for (InsnArg arg : this.getArguments()) { + if (arg.isInsnWrap()) { + return true; + } + } + return false; + } + /** * 'Soft' equals, don't compare arguments, only instruction specific parameters. */ diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/loops/ForEachLoop.java b/jadx-core/src/main/java/jadx/core/dex/regions/loops/ForEachLoop.java index ad575f26c..afae84c82 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/loops/ForEachLoop.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/loops/ForEachLoop.java @@ -1,25 +1,40 @@ package jadx.core.dex.regions.loops; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.InsnNode; public final class ForEachLoop extends LoopType { - private final RegisterArg varArg; - private final InsnArg iterableArg; + private final InsnNode varArgInsn; + private final InsnNode iterableArgInsn; public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) { - this.varArg = varArg; - this.iterableArg = iterableArg; + // store for-each args in fake instructions to + // save code semantics and allow args manipulations like args inlining + varArgInsn = new InsnNode(InsnType.REGION_ARG, 1); + varArgInsn.add(AFlag.DONT_INLINE); + varArgInsn.setResult(varArg.duplicate()); + + iterableArgInsn = new InsnNode(InsnType.REGION_ARG, 1); + iterableArgInsn.add(AFlag.DONT_INLINE); + iterableArgInsn.addArg(iterableArg.duplicate()); // will be declared at codegen - varArg.getSVar().getCodeVar().setDeclared(true); + getVarArg().getSVar().getCodeVar().setDeclared(true); + } + + public void injectFakeInsns(LoopRegion loopRegion) { + loopRegion.getInfo().getPreHeader().getInstructions().add(iterableArgInsn); + loopRegion.getHeader().getInstructions().add(0, varArgInsn); } public RegisterArg getVarArg() { - return varArg; + return varArgInsn.getResult(); } public InsnArg getIterableArg() { - return iterableArg; + return iterableArgInsn.getArg(0); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index 64fca3ce4..f758a1ee1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -511,11 +511,44 @@ public class BlockProcessor extends AbstractVisitor { LoopInfo loop = loops.get(0); return insertBlocksForBreak(mth, loop) || insertBlocksForContinue(mth, loop) - || insertBlockForProdecessors(mth, loop); + || insertBlockForProdecessors(mth, loop) + || insertPreHeader(mth, loop); } return false; } + /** + * Insert simple path block before loop header + */ + private static boolean insertPreHeader(MethodNode mth, LoopInfo loop) { + BlockNode start = loop.getStart(); + List preds = start.getPredecessors(); + int predsCount = preds.size() - 1; // don't count back edge + if (predsCount == 1) { + return false; + } + if (predsCount == 0) { + if (!start.contains(AFlag.MTH_ENTER_BLOCK)) { + mth.addWarnComment("Unexpected block without predecessors: " + start); + } + BlockNode newEnterBlock = BlockSplitter.startNewBlock(mth, -1); + newEnterBlock.add(AFlag.SYNTHETIC); + newEnterBlock.add(AFlag.MTH_ENTER_BLOCK); + mth.setEnterBlock(newEnterBlock); + start.remove(AFlag.MTH_ENTER_BLOCK); + BlockSplitter.connect(newEnterBlock, start); + return true; + } + // multiple predecessors + BlockNode preHeader = BlockSplitter.startNewBlock(mth, -1); + preHeader.add(AFlag.SYNTHETIC); + for (BlockNode pred : new ArrayList<>(preds)) { + BlockSplitter.replaceConnection(pred, start, preHeader); + } + BlockSplitter.connect(preHeader, start); + return true; + } + /** * Insert additional blocks for possible 'break' insertion */ 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 ea6dc4399..38b3c77ec 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 @@ -37,6 +37,8 @@ import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.BlockUtils; +import jadx.core.utils.InsnRemover; +import jadx.core.utils.InsnUtils; import jadx.core.utils.RegionUtils; import jadx.core.utils.exceptions.JadxOverflowException; @@ -50,7 +52,6 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor @Override public void visit(MethodNode mth) { - // DebugUtils.checkMethod(mth); DepthRegionTraversal.traverse(mth, this); } @@ -133,11 +134,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor incrInsn.add(AFlag.DONT_GENERATE); LoopType arrForEach = checkArrayForEach(mth, loopRegion, initInsn, incrInsn, condition); - if (arrForEach != null) { - loopRegion.setType(arrForEach); - } else { - loopRegion.setType(new ForLoop(initInsn, incrInsn)); - } + loopRegion.setType(arrForEach != null ? arrForEach : new ForLoop(initInsn, incrInsn)); return true; } @@ -172,7 +169,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor condArg = args.get(0); RegisterArg arrIndex = args.get(1); InsnNode arrGetInsn = arrIndex.getParentInsn(); - if (arrGetInsn == null || arrGetInsn.getType() != InsnType.AGET) { + if (arrGetInsn == null || arrGetInsn.getType() != InsnType.AGET || arrGetInsn.containsWrappedInsn()) { return null; } if (!condition.isCompare()) { @@ -211,23 +208,25 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor condArg.add(AFlag.DONT_GENERATE); bCondArg.add(AFlag.DONT_GENERATE); arrGetInsn.add(AFlag.DONT_GENERATE); - - // inline array variable - if (arrayArg.isRegister()) { - ((RegisterArg) arrayArg).getSVar().removeUse((RegisterArg) arrGetInsn.getArg(0)); - } - CodeShrinkVisitor.shrinkMethod(mth); - len.add(AFlag.DONT_GENERATE); + compare.getInsn().add(AFlag.DONT_GENERATE); if (arrGetInsn.contains(AFlag.WRAPPED)) { InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn); if (wrapArg != null && wrapArg.getParentInsn() != null) { - wrapArg.getParentInsn().replaceArg(wrapArg, iterVar); + InsnNode parentInsn = wrapArg.getParentInsn(); + parentInsn.replaceArg(wrapArg, iterVar.duplicate()); + parentInsn.rebindArgs(); } else { LOG.debug(" checkArrayForEach: Wrapped insn not found: {}, mth: {}", arrGetInsn, mth); } } - return new ForEachLoop(iterVar, len.getArg(0)); + ForEachLoop forEachLoop = new ForEachLoop(iterVar, len.getArg(0)); + forEachLoop.injectFakeInsns(loopRegion); + if (InsnUtils.dontGenerateIfNotUsed(len)) { + InsnRemover.remove(mth, len); + } + CodeShrinkVisitor.shrinkMethod(mth); + return forEachLoop; } private static boolean checkIterableForEach(MethodNode mth, LoopRegion loopRegion, IfCondition condition) { @@ -306,7 +305,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor for (RegisterArg itArg : itUseList) { itArg.add(AFlag.DONT_GENERATE); } - loopRegion.setType(new ForEachLoop(iterVar, iterableArg)); + ForEachLoop forEachLoop = new ForEachLoop(iterVar, iterableArg); + forEachLoop.injectFakeInsns(loopRegion); + loopRegion.setType(forEachLoop); return true; } diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java index d5ff17af0..54b944531 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java @@ -182,6 +182,8 @@ public class InsnRemover { BlockNode block = BlockUtils.getBlockByInsn(mth, insn); if (block != null) { remove(mth, block, insn); + } else { + mth.addWarnComment("Not found block with instruction: " + insn); } } 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 0d9170644..97ec70a1c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import com.android.dx.io.instructions.DecodedInstruction; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.ConstClassNode; @@ -17,6 +18,7 @@ import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; @@ -193,4 +195,20 @@ public class InsnUtils { } return null; } + + public static boolean dontGenerateIfNotUsed(InsnNode insn) { + RegisterArg resArg = insn.getResult(); + if (resArg != null) { + SSAVar ssaVar = resArg.getSVar(); + for (RegisterArg arg : ssaVar.getUseList()) { + InsnNode parentInsn = arg.getParentInsn(); + if (parentInsn != null + && !parentInsn.contains(AFlag.DONT_GENERATE)) { + return false; + } + } + } + insn.add(AFlag.DONT_GENERATE); + return true; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index 896eb446f..046f8ac0d 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -3,6 +3,11 @@ package jadx.core.utils; import jadx.api.JadxArgs; public class StringUtils { + private static final StringUtils DEFAULT_INSTANCE = new StringUtils(new JadxArgs()); + + public static StringUtils getInstance() { + return DEFAULT_INSTANCE; + } private final boolean escapeUnicode; diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakWithLabel.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakWithLabel.java index b30b5a74f..dc46f0f59 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakWithLabel.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakWithLabel.java @@ -36,7 +36,7 @@ public class TestBreakWithLabel extends IntegrationTest { } @Test - public void test() throws Exception { + public void test() { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java new file mode 100644 index 000000000..b61e01de2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java @@ -0,0 +1,19 @@ +package jadx.tests.integration.loops; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestLoopRestore extends SmaliTest { + + @Test + public void test() { + ClassNode cls = getClassNodeFromSmali(); + assertThat(cls).code() + .containsOne("try {") + .containsOne("for (byte b : digest) {"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java index d6e529026..f38168d15 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics2.java @@ -15,11 +15,9 @@ public class TestGenerics2 extends SmaliTest { public void test() { Map map = this.field; useInt(map.size()); - Iterator> it = map.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry next = it.next(); - useInt(next.getKey().intValue()); - next.getValue().trim(); + for (Map.Entry entry : map.entrySet()) { + useInt(entry.getKey().intValue()); + entry.getValue().trim(); } } */ @@ -30,8 +28,8 @@ public class TestGenerics2 extends SmaliTest { ClassNode cls = getClassNodeFromSmali(); String code = cls.getCode().toString(); - assertThat(code, containsOne("Entry next")); - assertThat(code, containsOne("useInt(next.getKey().intValue());")); // no Integer cast - assertThat(code, containsOne("next.getValue().trim();")); // no String cast + assertThat(code, containsOne("for (Map.Entry entry : map.entrySet()) {")); + assertThat(code, containsOne("useInt(entry.getKey().intValue());")); // no Integer cast + assertThat(code, containsOne("entry.getValue().trim();")); // no String cast } } diff --git a/jadx-core/src/test/smali/loops/TestLoopRestore.smali b/jadx-core/src/test/smali/loops/TestLoopRestore.smali new file mode 100644 index 000000000..f87f4ea26 --- /dev/null +++ b/jadx-core/src/test/smali/loops/TestLoopRestore.smali @@ -0,0 +1,74 @@ +.class public Lloops/TestLoopRestore; +.super Ljava/lang/Object; +.source "SourceFile.java" + +.method private test([B)Ljava/lang/String; + .registers 10 + + const/16 v0, 0x10 + new-array v0, v0, [C + fill-array-data v0, :array_3c + + :try_start_7 + const-string v1, "MD5" + invoke-static {v1}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest; + move-result-object v1 + + invoke-virtual {v1, p1}, Ljava/security/MessageDigest;->update([B)V + invoke-virtual {v1}, Ljava/security/MessageDigest;->digest()[B + move-result-object p1 + + array-length v1, p1 + mul-int/lit8 v2, v1, 0x2 + new-array v2, v2, [C + :try_end_19 + .catch Ljava/lang/Exception; {:try_start_7 .. :try_end_19} :catch_3a + + const/4 v3, 0x0 + const/4 v4, 0x0 + + :goto_1b + if-ge v3, v1, :cond_34 + + aget-byte v5, p1, v3 + add-int/lit8 v6, v4, 0x1 + ushr-int/lit8 v7, v5, 0x4 + and-int/lit8 v7, v7, 0xf + aget-char v7, v0, v7 + aput-char v7, v2, v4 + add-int/lit8 v4, v6, 0x1 + and-int/lit8 v5, v5, 0xf + aget-char v5, v0, v5 + aput-char v5, v2, v6 + add-int/lit8 v3, v3, 0x1 + goto :goto_1b + + :cond_34 + new-instance p1, Ljava/lang/String; + invoke-direct {p1, v2}, Ljava/lang/String;->([C)V + return-object p1 + + :catch_3a + const/4 p1, 0x0 + return-object p1 + + :array_3c + .array-data 2 + 0x30s + 0x31s + 0x32s + 0x33s + 0x34s + 0x35s + 0x36s + 0x37s + 0x38s + 0x39s + 0x61s + 0x62s + 0x63s + 0x64s + 0x65s + 0x66s + .end array-data +.end method From c62039f327c7cb0628b4ecd8a828c72f71293722 Mon Sep 17 00:00:00 2001 From: yunlong17568 Date: Sun, 12 Apr 2020 01:41:27 +0800 Subject: [PATCH 11/55] fix(gui): use env %JAVA_HOME% as the JRE path (PR #895) Co-authored-by: yunlong.yang --- jadx-gui/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 27cf4fa17..934cdda12 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -70,6 +70,7 @@ launch4j { maxHeapSize = 4096 maxHeapPercent = 70 downloadUrl = 'https://adoptopenjdk.net/releases.html?variant=openjdk11&jvmVariant=hotspot#x64_win' + bundledJrePath = '%JAVA_HOME%' } test { From 47dadf0a436f4e8f1cf47152bd63e4689503dd17 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 18 Apr 2020 17:23:34 +0100 Subject: [PATCH 12/55] fix: use correct class for Throwable, insert catch arg name if not defined (#896) --- jadx-core/src/main/java/jadx/core/codegen/RegionGen.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 5fdd65e57..f04002e3d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -15,6 +15,7 @@ import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.NamedArg; @@ -334,7 +335,7 @@ public class RegionGen extends InsnGen { } code.startLine("} catch ("); if (handler.isCatchAll()) { - code.add("Throwable"); + useClass(code, ArgType.THROWABLE); } else { Iterator it = handler.getCatchTypes().iterator(); if (it.hasNext()) { @@ -347,11 +348,15 @@ public class RegionGen extends InsnGen { } code.add(' '); InsnArg arg = handler.getArg(); - if (arg instanceof RegisterArg) { + if (arg == null) { + code.add("unknown"); // throwing exception is too late at this point + } else if (arg instanceof RegisterArg) { RegisterArg reg = (RegisterArg) arg; code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar())); } else if (arg instanceof NamedArg) { code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg)); + } else { + throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName()); } code.add(") {"); makeRegionIndent(code, region); From 315c07d4f68c33f1372d2cb8419cb8879f313794 Mon Sep 17 00:00:00 2001 From: skylot Date: Tue, 21 Apr 2020 22:33:35 +0300 Subject: [PATCH 13/55] feat(res): rename resources keys if contains unprintable chars or duplicates (#844) (PR #909) --- .../java/jadx/core/xmlgen/ResTableParser.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java index d286d9370..126969a1e 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; import jadx.core.codegen.CodeWriter; +import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.RootNode; @@ -152,6 +153,7 @@ public class ResTableParser extends CommonBinaryParser { if (keyStringsOffset != 0) { is.skipToPos(keyStringsOffset, "Expected keyStrings string pool"); keyStrings = parseStringPool(); + deobfKeyStrings(keyStrings); } PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings); @@ -172,6 +174,32 @@ public class ResTableParser extends CommonBinaryParser { return pkg; } + private void deobfKeyStrings(String[] keyStrings) { + int keysCount = keyStrings.length; + if (root.getArgs().isRenamePrintable()) { + for (int i = 0; i < keysCount; i++) { + String keyString = keyStrings[i]; + if (!NameMapper.isAllCharsPrintable(keyString)) { + keyStrings[i] = makeNewKeyName(i); + } + } + } + if (root.getArgs().isRenameValid()) { + Set keySet = new HashSet<>(keysCount); + for (int i = 0; i < keysCount; i++) { + String keyString = keyStrings[i]; + boolean isNew = keySet.add(keyString); + if (!isNew) { + keyStrings[i] = makeNewKeyName(i); + } + } + } + } + + private String makeNewKeyName(int idx) { + return "JADX_DEOBF_" + idx; + } + @SuppressWarnings("unused") private void parseTypeSpecChunk() throws IOException { is.checkInt16(0x0010, "Unexpected type spec header size"); From 83196628c9a445206924a013523f675a5e72bd72 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 24 Apr 2020 13:23:58 +0100 Subject: [PATCH 14/55] docs: fix issue and PR templates --- .github/ISSUE_TEMPLATE/decompilation-error.md | 8 +++----- .github/pull_request_template.md | 4 ++-- CONTRIBUTING.md | 11 +++++------ README.md | 2 -- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/decompilation-error.md b/.github/ISSUE_TEMPLATE/decompilation-error.md index ef763a65c..27516b57c 100644 --- a/.github/ISSUE_TEMPLATE/decompilation-error.md +++ b/.github/ISSUE_TEMPLATE/decompilation-error.md @@ -13,10 +13,8 @@ assignees: '' - search existing issues by exception message **Describe error** -- provide full name of method or class with error -- provide full java stacktrace - - **Note**: no need to copy method fallback code (commented pseudocode) -- attach or provide link to apk file (double check apk version) +- full name of method or class with error +- full java stacktrace (no need to copy method fallback code (commented pseudocode)) +- **IMPORTANT!** attach or provide link to apk file (double check apk version) **Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 65dc0edf9..12ce91efb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,5 @@ -:exclamation: Please review the [guidelines for contributing](../CONTRIBUTING.md#Pull-Request-Process) +:exclamation: Please review the [guidelines for contributing](https://github.com/skylot/jadx/blob/master/CONTRIBUTING.md#Pull-Request-Process) ### Description Please describe your pull request. -Reference issue it fix. +Reference issue it fixes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 561caed2a..a08fbd08a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,11 +10,10 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in - search existing issues by exception message 2. Describe error - - provide full name of method or class with error - - provide full java stacktrace - - **Note**: no need to copy method fallback code (commented pseudocode) - - attach or provide link to apk file (double check apk version) + **Describe error** + - full name of method or class with error + - full java stacktrace (no need to copy method fallback code (commented pseudocode)) + - **IMPORTANT!:** attach or provide link to apk file (double check apk version) **Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :) @@ -23,7 +22,7 @@ Please note we have a [code of conduct](CODE_OF_CONDUCT.md), please follow it in 1. Please don't submit any code style fixes, dependencies updates or other changes which are not fixing any issues. -1. Before open a PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected. +1. Before start working on PR please discuss the change you wish to make via issue. PR without corresponding issue will be rejected. 1. Use only features and API from Java 8 or below. diff --git a/README.md b/README.md index de7e6cdff..963a1fced 100644 --- a/README.md +++ b/README.md @@ -121,5 +121,3 @@ To support this project you can: --------------------------------------- *Licensed under the Apache 2.0 License* - -*Copyright 2019 by Skylot* From e3f388af1167ccd33bbdcb5b11215cb4742bd2ed Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 25 Apr 2020 19:15:14 +0100 Subject: [PATCH 15/55] fix(deobf): resolve NPE when package is empty (if rename is disabled) --- .../main/java/jadx/core/deobf/PackageNode.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java index e5fd1debc..ccd2662a1 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java +++ b/jadx-core/src/main/java/jadx/core/deobf/PackageNode.java @@ -31,14 +31,17 @@ public class PackageNode { public String getFullName() { if (cachedPackageFullName == null) { Deque pp = getParentPackages(); - - StringBuilder result = new StringBuilder(); - result.append(pp.pop().getName()); - while (!pp.isEmpty()) { - result.append(SEPARATOR_CHAR); + if (pp.isEmpty()) { + cachedPackageFullName = ""; + } else { + StringBuilder result = new StringBuilder(); result.append(pp.pop().getName()); + while (!pp.isEmpty()) { + result.append(SEPARATOR_CHAR); + result.append(pp.pop().getName()); + } + cachedPackageFullName = result.toString(); } - cachedPackageFullName = result.toString(); } return cachedPackageFullName; } From 4dc4aa122b0f156be71278d532687e6c5f43da41 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 26 Apr 2020 18:25:49 +0100 Subject: [PATCH 16/55] fix: don't duplicate result arg with instruction copy (breaks SSA variable assign) --- jadx-core/src/main/java/jadx/core/Jadx.java | 1 + .../main/java/jadx/core/codegen/InsnGen.java | 20 +++--- .../java/jadx/core/dex/attributes/AFlag.java | 1 - .../java/jadx/core/dex/attributes/AType.java | 3 + .../dex/attributes/nodes/EnumClassAttr.java | 5 +- .../dex/attributes/nodes/GenericInfoAttr.java | 31 +++++++++ .../nodes/NotificationAttrNode.java | 5 ++ .../jadx/core/dex/instructions/ArithNode.java | 11 ++-- .../core/dex/instructions/args/InsnArg.java | 38 +++++++---- .../dex/instructions/args/InsnWrapArg.java | 2 +- .../dex/instructions/args/RegisterArg.java | 4 ++ .../core/dex/instructions/args/SSAVar.java | 1 + .../java/jadx/core/dex/nodes/InsnNode.java | 66 ++++++++++++++++--- .../java/jadx/core/dex/nodes/MethodNode.java | 3 +- .../core/dex/visitors/ConstInlineVisitor.java | 3 +- .../jadx/core/dex/visitors/EnumVisitor.java | 18 ++--- .../dex/visitors/GenericTypesVisitor.java | 61 +++++++++++++++++ .../dex/visitors/MethodInlineVisitor.java | 2 +- .../core/dex/visitors/SimplifyVisitor.java | 10 ++- .../visitors/blocksmaker/BlockProcessor.java | 2 +- .../visitors/regions/LoopRegionVisitor.java | 46 ++++++++----- .../visitors/shrink/CodeShrinkVisitor.java | 17 ++--- .../core/dex/visitors/ssa/SSATransform.java | 1 - .../typeinference/TypeInferenceVisitor.java | 4 +- .../java/jadx/core/utils/InsnRemover.java | 49 +++++++++++--- .../main/java/jadx/core/utils/InsnUtils.java | 2 +- .../exceptions/JadxRuntimeException.java | 10 +++ 27 files changed, 319 insertions(+), 97 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index fd8162f90..1a05c5986 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -71,6 +71,7 @@ public class Jadx { passes.add(new DebugInfoApplyVisitor()); } + passes.add(new GenericTypesVisitor()); passes.add(new DeboxingVisitor()); passes.add(new ModVisitor()); passes.add(new CodeShrinkVisitor()); 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 1238d1a75..2a5b3012a 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -15,6 +15,7 @@ import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; +import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; @@ -603,8 +604,7 @@ public class InsnGen { code.add('}'); } - private void makeConstructor(ConstructorInsn insn, CodeWriter code) - throws CodegenException { + private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException { ClassNode cls = mth.dex().resolveClass(insn.getClassType()); if (cls != null && cls.isAnonymous() && !fallback) { cls.ensureProcessed(); @@ -622,20 +622,18 @@ public class InsnGen { } else { code.add("new "); useClass(code, insn.getClassType()); - ArgType argType = insn.getResult().getSVar().getCodeVar().getType(); - boolean genericCls = cls == null || !cls.getGenericTypeParameters().isEmpty(); - if (argType != null - && argType.getGenericTypes() != null - && genericCls) { + GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO); + if (genericInfoAttr != null) { code.add('<'); - if (insn.contains(AFlag.EXPLICIT_GENERICS)) { + if (genericInfoAttr.isExplicit()) { boolean first = true; - for (ArgType type : argType.getGenericTypes()) { + for (ArgType type : genericInfoAttr.getGenericTypes()) { if (!first) { code.add(','); + } else { + first = false; } mgen.getClassGen().useType(code, type); - first = false; } } code.add('>'); @@ -849,7 +847,7 @@ public class InsnGen { regs[regNums[i]] = arg; } // replace args - InsnNode inlCopy = inl.copy(); + InsnNode inlCopy = inl.copyWithoutResult(); List inlArgs = new ArrayList<>(); inlCopy.getRegisterArgs(inlArgs); for (RegisterArg r : inlArgs) { diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 85d54f45c..8f5687f01 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -60,7 +60,6 @@ public enum AFlag { FALL_THROUGH, - EXPLICIT_GENERICS, VARARG_CALL, /** 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 00d018af4..6003ae9c8 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 @@ -12,6 +12,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumMapAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr; +import jadx.core.dex.attributes.nodes.GenericInfoAttr; import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JumpInfo; @@ -36,6 +37,7 @@ import jadx.core.dex.trycatch.SplitterBlockAttr; * * @param attribute class implementation */ +@SuppressWarnings("InstantiationOfUtilityClass") public class AType { // class, method, field @@ -81,6 +83,7 @@ public class AType { public static final AType LOOP_LABEL = new AType<>(); public static final AType> JUMP = new AType<>(); public static final AType METHOD_DETAILS = new AType<>(); + public static final AType GENERIC_INFO = new AType<>(); // register public static final AType REG_DEBUG_INFO = new AType<>(); 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 53f72877c..dd9f24c50 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 @@ -1,6 +1,5 @@ package jadx.core.dex.attributes.nodes; -import java.util.ArrayList; import java.util.List; import jadx.core.dex.attributes.AType; @@ -53,8 +52,8 @@ public class EnumClassAttr implements IAttribute { private final List fields; private MethodNode staticMethod; - public EnumClassAttr(int fieldsCount) { - this.fields = new ArrayList<>(fieldsCount); + public EnumClassAttr(List fields) { + this.fields = fields; } public List getFields() { 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 new file mode 100644 index 000000000..5466919a9 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/GenericInfoAttr.java @@ -0,0 +1,31 @@ +package jadx.core.dex.attributes.nodes; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.instructions.args.ArgType; + +public class GenericInfoAttr implements IAttribute { + private final ArgType[] genericTypes; + private boolean explicit; + + public GenericInfoAttr(ArgType[] genericTypes) { + this.genericTypes = genericTypes; + } + + public ArgType[] getGenericTypes() { + return genericTypes; + } + + public boolean isExplicit() { + return explicit; + } + + public void setExplicit(boolean explicit) { + this.explicit = explicit; + } + + @Override + public AType getType() { + return AType.GENERIC_INFO; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java index c2f72087b..4f2f55261 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java @@ -37,4 +37,9 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode addAttr(AType.COMMENTS, commentStr); LOG.info("{} in {}", commentStr, this); } + + public void addDebugComment(String commentStr) { + addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr); + LOG.debug("{} in {}", commentStr, this); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java index a2717ff2e..6346acc99 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java @@ -51,6 +51,10 @@ public class ArithNode extends InsnNode { addArg(b); } + public ArithNode(ArithOp op, InsnArg a, InsnArg b) { + this(op, null, a, b); + } + /** * Create one argument arithmetic instructions (a+=2). * Result is not set (null). @@ -58,7 +62,7 @@ public class ArithNode extends InsnNode { * @param res argument to change */ public static ArithNode oneArgOp(ArithOp op, InsnArg res, InsnArg a) { - ArithNode insn = new ArithNode(op, null, res, a); + ArithNode insn = new ArithNode(op, res, a); insn.add(AFlag.ARITH_ONEARG); return insn; } @@ -97,10 +101,7 @@ public class ArithNode extends InsnNode { @Override public InsnNode copy() { - ArithNode copy = new ArithNode(op, - getResult().duplicate(), - getArg(0).duplicate(), - getArg(1).duplicate()); + ArithNode copy = new ArithNode(op, getArg(0).duplicate(), getArg(1).duplicate()); return copyCommonParams(copy); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index cf35cc5eb..207452a82 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -1,5 +1,6 @@ package jadx.core.dex.instructions.args; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,6 +97,11 @@ public abstract class InsnArg extends Typed { @Nullable("if wrap failed") public InsnArg wrapInstruction(MethodNode mth, InsnNode insn) { + return wrapInstruction(mth, insn, true); + } + + @Nullable("if wrap failed") + public InsnArg wrapInstruction(MethodNode mth, InsnNode insn, boolean unbind) { InsnNode parent = parentInsn; if (parent == null) { return null; @@ -116,14 +122,24 @@ public abstract class InsnArg extends Typed { if (arg.isRegister()) { ((RegisterArg) arg).setNameIfUnknown(name); } else if (arg.isInsnWrap()) { - ((InsnWrapArg) arg).getWrapInsn().getResult().setNameIfUnknown(name); + InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); + RegisterArg registerArg = wrapInsn.getResult(); + if (registerArg != null) { + registerArg.setNameIfUnknown(name); + } } } } InsnArg arg = wrapInsnIntoArg(insn); + InsnArg oldArg = parent.getArg(i); parent.setArg(i, arg); - InsnRemover.unbindArgUsage(mth, this); - InsnRemover.unbindResult(mth, insn); + InsnRemover.unbindArgUsage(mth, oldArg); + if (unbind) { + InsnRemover.unbindArgUsage(mth, this); + // result not needed in wrapped insn + InsnRemover.unbindResult(mth, insn); + insn.setResult(null); + } return arg; } @@ -137,29 +153,29 @@ public abstract class InsnArg extends Typed { return -1; } + @NotNull public static InsnArg wrapInsnIntoArg(InsnNode insn) { - InsnArg arg; InsnType type = insn.getType(); if (type == InsnType.CONST || type == InsnType.MOVE) { if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) { RegisterArg resArg = insn.getResult(); - arg = wrap(insn); + InsnArg arg = wrap(insn); if (resArg != null) { arg.setType(resArg.getType()); } + return arg; } else { - arg = insn.getArg(0); - insn.add(AFlag.REMOVE); + InsnArg arg = insn.getArg(0); insn.add(AFlag.DONT_GENERATE); + return arg; } - } else { - arg = wrapArg(insn); } - return arg; + return wrapArg(insn); } /** - * Prefer {@link InsnArg#wrapInsnIntoArg}. + * Prefer {@link InsnArg#wrapInsnIntoArg(InsnNode)}. + *

* This method don't support MOVE and CONST insns! */ public static InsnArg wrapArg(InsnNode insn) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java index 70d61333a..186aa2b19 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java @@ -36,7 +36,7 @@ public final class InsnWrapArg extends InsnArg { @Override public InsnArg duplicate() { - InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copy()); + InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copyWithoutResult()); copy.setType(type); return copyCommonParams(copy); } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index 6b8ada9d4..ca9fe1ce9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -130,6 +130,10 @@ public class RegisterArg extends InsnArg implements Named { return duplicate(getRegNum(), sVar); } + public RegisterArg duplicate(@Nullable SSAVar ssaVar) { + return duplicate(getRegNum(), ssaVar); + } + public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) { RegisterArg dup = new RegisterArg(regNum, getInitType()); if (sVar != null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java index 6d8a10d7d..c005f9e0c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java @@ -270,6 +270,7 @@ public class SSAVar { if (!types.isEmpty()) { sb.append(", types: ").append(types); } + sb.append(", assign insn: ").append(getAssign().getParentInsn()); return sb.toString(); } } 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 71aff0ad4..98b3b7070 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 @@ -51,6 +51,7 @@ public class InsnNode extends LineAttrNode { } public void setResult(@Nullable RegisterArg res) { + this.result = res; if (res != null) { res.setParentInsn(this); SSAVar ssaVar = res.getSVar(); @@ -58,7 +59,6 @@ public class InsnNode extends LineAttrNode { ssaVar.setAssign(res); } } - this.result = res; } public void addArg(InsnArg arg) { @@ -326,17 +326,9 @@ public class InsnNode extends LineAttrNode { } protected final T copyCommonParams(T copy) { - if (copy.getResult() == null && result != null) { - copy.setResult(result.duplicate()); - } if (copy.getArgsCount() == 0) { for (InsnArg arg : this.getArguments()) { - if (arg.isInsnWrap()) { - InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); - copy.addArg(InsnArg.wrapInsnIntoArg(wrapInsn.copy())); - } else { - copy.addArg(arg.duplicate()); - } + copy.addArg(arg.duplicate()); } } copy.copyAttributesFrom(this); @@ -347,6 +339,17 @@ public class InsnNode extends LineAttrNode { /** * Make copy of InsnNode object. + *

+ * NOTE: can't copy instruction with result argument + * (SSA variable can't be used in two different assigns). + *

+ * Prefer use next methods: + *

    + *
  • {@link #copyWithoutResult()} to explicitly state that result not needed + *
  • {@link #copy(RegisterArg)} to provide new result arg + *
  • {@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg + *
+ *

*/ public InsnNode copy() { if (this.getClass() != InsnNode.class) { @@ -355,6 +358,49 @@ public class InsnNode extends LineAttrNode { return copyCommonParams(new InsnNode(insnType, getArgsCount())); } + /** + * See {@link #copy()} + */ + @SuppressWarnings("unchecked") + public T copyWithoutResult() { + return (T) copy(); + } + + public InsnNode copyWithoutSsa() { + InsnNode copy = copyWithoutResult(); + if (result != null) { + if (result.getSVar() == null) { + copy.setResult(result.duplicate()); + } else { + throw new JadxRuntimeException("Can't copy if SSA var is set"); + } + } + return copy; + } + + /** + * See {@link #copy()} + */ + public InsnNode copy(RegisterArg newReturnArg) { + InsnNode copy = copy(); + copy.setResult(newReturnArg); + return copy; + } + + /** + * See {@link #copy()} + */ + public InsnNode copyWithNewSsaVar(MethodNode mth) { + RegisterArg result = getResult(); + if (result == null) { + throw new JadxRuntimeException("Result in null"); + } + int regNum = result.getRegNum(); + RegisterArg resDupArg = result.duplicate(regNum, null); + mth.makeNewSVar(resDupArg); + return copy(resDupArg); + } + /** * Fix SSAVar info in register arguments. * Must be used after altering instructions. 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 7e30b07af..6aea15ab7 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 @@ -647,7 +647,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return debugInfoOffset; } - public SSAVar makeNewSVar(int regNum, @NotNull RegisterArg assignArg) { + public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) { + int regNum = assignArg.getRegNum(); return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg); } 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 4dfe07854..a358b7628 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 @@ -93,8 +93,7 @@ public class ConstInlineVisitor extends AbstractVisitor { String s = ((ConstStringNode) insn).getString(); FieldNode f = mth.getParentClass().getConstField(s); if (f == null) { - InsnNode copy = insn.copy(); - copy.setResult(null); + InsnNode copy = insn.copyWithoutResult(); constArg = InsnArg.wrapArg(copy); } else { InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index edfe3fd4b..a5f8b428c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -44,6 +44,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.BlockInsnPair; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxException; import static jadx.core.utils.InsnUtils.checkInsnType; @@ -152,14 +153,13 @@ public class EnumVisitor extends AbstractVisitor { toRemove.add(valuesInitInsn); // all checks complete, perform transform - EnumClassAttr attr = new EnumClassAttr(enumFields.size()); + EnumClassAttr attr = new EnumClassAttr(enumFields); attr.setStaticMethod(classInitMth); - attr.getFields().addAll(enumFields); cls.addAttr(attr); - for (EnumField field : attr.getFields()) { - ConstructorInsn co = field.getConstrInsn(); - FieldNode fieldNode = field.getField(); + for (EnumField enumField : attr.getFields()) { + ConstructorInsn co = enumField.getConstrInsn(); + FieldNode fieldNode = enumField.getField(); // use string arg from the constructor as enum field name String name = getConstString(cls.dex(), co.getArg(0)); @@ -172,13 +172,16 @@ public class EnumVisitor extends AbstractVisitor { if (!co.getClassType().equals(cls.getClassInfo())) { // enum contains additional methods for (ClassNode innerCls : cls.getInnerClasses()) { - processEnumInnerCls(co, field, innerCls); + processEnumInnerCls(co, enumField, innerCls); } } + fieldNode.add(AFlag.DONT_GENERATE); } + List constrInsns = Utils.collectionMap(attr.getFields(), EnumField::getConstrInsn); + InsnRemover.removeAllWithoutUnbind(staticBlock, constrInsns); + valuesField.add(AFlag.DONT_GENERATE); - enumFields.forEach(f -> f.getField().add(AFlag.DONT_GENERATE)); InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove); if (classInitMth.countInsns() == 0) { classInitMth.add(AFlag.DONT_GENERATE); @@ -242,7 +245,6 @@ public class EnumVisitor extends AbstractVisitor { } toRemove.add(sgetInsn); toRemove.add(sputInsn); - toRemove.add(co); return createEnumFieldByConstructor(cls, enumFieldNode, co); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java new file mode 100644 index 000000000..f58e0391e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java @@ -0,0 +1,61 @@ +package jadx.core.dex.visitors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.dex.attributes.nodes.GenericInfoAttr; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.mods.ConstructorInsn; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; +import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; +import jadx.core.utils.exceptions.JadxException; + +@JadxVisitor( + name = "GenericTypesVisitor", + desc = "Fix and apply generic type info", + runAfter = TypeInferenceVisitor.class, + runBefore = { CodeShrinkVisitor.class, MethodInvokeVisitor.class } +) +public class GenericTypesVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(GenericTypesVisitor.class); + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + if (insn.getType() == InsnType.CONSTRUCTOR) { + attachGenericTypesInfo(mth, (ConstructorInsn) insn); + } + } + } + } + + private void attachGenericTypesInfo(MethodNode mth, ConstructorInsn insn) { + try { + RegisterArg resultArg = insn.getResult(); + if (resultArg == null) { + return; + } + ArgType argType = resultArg.getSVar().getCodeVar().getType(); + if (argType == null || argType.getGenericTypes() == null) { + return; + } + ClassNode cls = mth.dex().resolveClass(insn.getClassType()); + if (cls != null && cls.getGenericTypeParameters().isEmpty()) { + return; + } + insn.addAttr(new GenericInfoAttr(argType.getGenericTypes())); + } catch (Exception e) { + LOG.error("Failed to attach constructor generic info", e); + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index c8cdb960e..993b65fb1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -73,7 +73,7 @@ public class MethodInlineVisitor extends AbstractVisitor { if (Consts.DEBUG) { mth.addAttr(AType.COMMENTS, "Removed for inline"); } else { - InsnNode copy = insn.copy(); + InsnNode copy = insn.copyWithoutResult(); // unbind SSA variables from copy instruction List regArgs = new ArrayList<>(); copy.getRegisterArgs(regArgs); 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 33e487345..09ae30f4a 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 @@ -39,6 +39,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.BlockUtils; import jadx.core.utils.InsnList; import jadx.core.utils.InsnRemover; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; public class SimplifyVisitor extends AbstractVisitor { @@ -393,7 +394,8 @@ public class SimplifyVisitor extends AbstractVisitor { // all check passed removeStringBuilderInsns(mth, toStrInsn, chain); - InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, args); + List dupArgs = Utils.collectionMap(args, InsnArg::duplicate); + InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, dupArgs); concatInsn.setResult(toStrInsn.getResult()); concatInsn.add(AFlag.SYNTHETIC); concatInsn.copyAttributesFrom(toStrInsn); @@ -499,7 +501,8 @@ public class SimplifyVisitor extends AbstractVisitor { || !wrap.getArg(0).isInsnWrap()) { return null; } - InsnNode get = ((InsnWrapArg) wrap.getArg(0)).getWrapInsn(); + InsnArg getWrap = wrap.getArg(0); + InsnNode get = ((InsnWrapArg) getWrap).getWrapInsn(); InsnType getType = get.getType(); if (getType != InsnType.IGET && getType != InsnType.SGET) { return null; @@ -517,7 +520,8 @@ public class SimplifyVisitor extends AbstractVisitor { return null; } } - InsnArg fArg = InsnArg.wrapArg(get); + InsnArg fArg = getWrap.duplicate(); + InsnRemover.unbindInsn(mth, get); if (insn.getType() == InsnType.IPUT) { InsnRemover.unbindArgUsage(mth, insn.getArg(1)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index f758a1ee1..0f9c929ed 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -750,7 +750,7 @@ public class BlockProcessor extends AbstractVisitor { first = false; } else { for (InsnNode oldInsn : exitBlock.getInstructions()) { - newRetBlock.getInstructions().add(oldInsn.copy()); + newRetBlock.getInstructions().add(oldInsn.copyWithoutSsa()); } } BlockSplitter.replaceConnection(pred, exitBlock, newRetBlock); 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 38b3c77ec..ce562c236 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 @@ -255,27 +255,19 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor return false; } List toSkip = new LinkedList<>(); - RegisterArg iterVar = nextCall.getResult(); - if (iterVar == null) { - return false; - } - if (!usedOnlyInLoop(mth, loopRegion, iterVar)) { - return false; - } - if (!assignOnlyInLoop(mth, loopRegion, iterVar)) { - return false; - } - + RegisterArg iterVar; if (nextCall.contains(AFlag.WRAPPED)) { InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall); if (wrapArg != null && wrapArg.getParentInsn() != null) { InsnNode parentInsn = wrapArg.getParentInsn(); - if (parentInsn.getType() != InsnType.CHECK_CAST) { - if (!fixIterableType(mth, iterableArg, iterVar)) { - return false; - } - parentInsn.replaceArg(wrapArg, iterVar); - } else { + BlockNode block = BlockUtils.getBlockByInsn(mth, parentInsn); + if (block == null) { + return false; + } + if (!RegionUtils.isRegionContainsBlock(loopRegion, block)) { + return false; + } + if (parentInsn.getType() == InsnType.CHECK_CAST) { iterVar = parentInsn.getResult(); if (iterVar == null || !fixIterableType(mth, iterableArg, iterVar)) { return false; @@ -287,12 +279,32 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor // cast not inlined toSkip.add(parentInsn); } + } else { + iterVar = nextCall.getResult(); + if (iterVar == null) { + return false; + } + nextCall.add(AFlag.DONT_GENERATE); + if (!fixIterableType(mth, iterableArg, iterVar)) { + return false; + } + parentInsn.replaceArg(wrapArg, iterVar); } } else { LOG.warn(" checkIterableForEach: Wrapped insn not found: {}, mth: {}", nextCall, mth); return false; } } else { + iterVar = nextCall.getResult(); + if (iterVar == null) { + return false; + } + if (!usedOnlyInLoop(mth, loopRegion, iterVar)) { + return false; + } + if (!assignOnlyInLoop(mth, loopRegion, iterVar)) { + return false; + } toSkip.add(nextCall); } 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 40d731ce7..694aa7042 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 @@ -25,7 +25,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; @JadxVisitor( name = "CodeShrinkVisitor", - desc = "Inline variables for make code smaller", + desc = "Inline variables to make code smaller", runAfter = { ModVisitor.class } ) public class CodeShrinkVisitor extends AbstractVisitor { @@ -125,12 +125,11 @@ public class CodeShrinkVisitor extends AbstractVisitor { if (useInsn == null || useInsn.contains(AFlag.DONT_GENERATE)) { return false; } - InsnArg replaceArg = InsnArg.wrapInsnIntoArg(assignInsn.copy()); + if (!InsnRemover.removeWithoutUnbind(mth, assignBlock, assignInsn)) { + return false; + } + InsnArg replaceArg = InsnArg.wrapInsnIntoArg(assignInsn); useInsn.replaceArg(useArg, replaceArg); - - assignInsn.add(AFlag.REMOVE); - assignInsn.add(AFlag.DONT_GENERATE); - InsnRemover.remove(mth, assignBlock, assignInsn); return true; } @@ -142,9 +141,11 @@ public class CodeShrinkVisitor extends AbstractVisitor { if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) { return assignInline(mth, arg, insn, block); } - boolean replaced = arg.wrapInstruction(mth, insn) != null; + // just move instruction into arg, don't unbind/copy/duplicate + InsnArg wrappedArg = arg.wrapInstruction(mth, insn, false); + boolean replaced = wrappedArg != null; if (replaced) { - InsnList.remove(block, insn); + InsnRemover.removeWithoutUnbind(mth, block, insn); } return replaced; } 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 8451d6031..5dfc501e3 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 @@ -422,7 +422,6 @@ public class SSATransform extends AbstractVisitor { if (resArg.getRegNum() != arg.getRegNum() && !resArg.getSVar().isUsedInPhi()) { markThisArgs(resArg); - parentInsn.add(AFlag.REMOVE); parentInsn.add(AFlag.DONT_GENERATE); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index a86e3f4c7..7979a3e24 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -404,7 +404,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (apply) { int regNum = reg.getRegNum(); RegisterArg resultArg = reg.duplicate(regNum, null); - SSAVar newSsaVar = mth.makeNewSVar(regNum, resultArg); + SSAVar newSsaVar = mth.makeNewSVar(resultArg); RegisterArg arg = reg.duplicate(regNum, var); InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1); @@ -476,7 +476,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { castNode.addArg(bound.getArg()); castNode.setResult(InsnArg.reg(bound.getArg().getRegNum(), bound.getType())); - SSAVar newVar = mth.makeNewSVar(castNode.getResult().getRegNum(), castNode.getResult()); + SSAVar newVar = mth.makeNewSVar(castNode.getResult()); CodeVar codeVar = new CodeVar(); codeVar.setType(bound.getType()); newVar.setCodeVar(codeVar); diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java index 54b944531..623d4a1dc 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java @@ -56,8 +56,6 @@ public class InsnRemover { public void addWithoutUnbind(InsnNode insn) { toRemove.add(insn); - insn.add(AFlag.REMOVE); - insn.add(AFlag.DONT_GENERATE); } public void perform() { @@ -69,7 +67,8 @@ public class InsnRemover { remove(mth, remInsn); } } else { - removeAll(mth, instrList, toRemove); + unbindInsns(mth, toRemove); + removeAll(instrList, toRemove); } toRemove.clear(); } @@ -77,10 +76,15 @@ public class InsnRemover { public static void unbindInsn(@Nullable MethodNode mth, InsnNode insn) { unbindAllArgs(mth, insn); unbindResult(mth, insn); - insn.add(AFlag.REMOVE); insn.add(AFlag.DONT_GENERATE); } + public static void unbindInsns(@Nullable MethodNode mth, List insns) { + for (InsnNode insn : insns) { + unbindInsn(mth, insn); + } + } + public static void unbindAllArgs(@Nullable MethodNode mth, InsnNode insn) { for (InsnArg arg : insn.getArguments()) { unbindArgUsage(mth, arg); @@ -154,7 +158,7 @@ public class InsnRemover { // Don't use 'instrList.removeAll(toRemove)' because it will remove instructions by content // and here can be several instructions with same content - private static void removeAll(MethodNode mth, List insns, List toRemove) { + private static void removeAll(List insns, List toRemove) { if (toRemove == null || toRemove.isEmpty()) { return; } @@ -164,7 +168,6 @@ public class InsnRemover { for (int i = 0; i < insnsCount; i++) { if (insns.get(i) == rem) { insns.remove(i); - unbindInsn(mth, rem); found = true; break; } @@ -179,32 +182,58 @@ public class InsnRemover { } public static void remove(MethodNode mth, InsnNode insn) { + if (insn.contains(AFlag.WRAPPED)) { + unbindInsn(mth, insn); + return; + } BlockNode block = BlockUtils.getBlockByInsn(mth, insn); if (block != null) { remove(mth, block, insn); } else { + insn.add(AFlag.DONT_GENERATE); mth.addWarnComment("Not found block with instruction: " + insn); } } public static void remove(MethodNode mth, BlockNode block, InsnNode insn) { unbindInsn(mth, insn); + removeWithoutUnbind(mth, block, insn); + } + + public static boolean removeWithoutUnbind(MethodNode mth, BlockNode block, InsnNode insn) { // remove by pointer (don't use equals) Iterator it = block.getInstructions().iterator(); while (it.hasNext()) { InsnNode ir = it.next(); if (ir == insn) { it.remove(); - return; + return true; } } + mth.addWarnComment("Failed to remove instruction: " + insn + " from block: " + block); + return false; } public static void removeAllAndUnbind(MethodNode mth, BlockNode block, List insns) { - for (InsnNode insn : insns) { - unbindInsn(mth, insn); + unbindInsns(mth, insns); + removeAll(block.getInstructions(), insns); + } + + public static void removeAllWithoutUnbind(BlockNode block, List insns) { + removeAll(block.getInstructions(), insns); + } + + public static void removeAllMarked(MethodNode mth) { + InsnRemover insnRemover = new InsnRemover(mth); + for (BlockNode blockNode : mth.getBasicBlocks()) { + for (InsnNode insn : blockNode.getInstructions()) { + if (insn.contains(AFlag.REMOVE)) { + insnRemover.addWithoutUnbind(insn); + } + } + insnRemover.setBlock(blockNode); + insnRemover.perform(); } - removeAll(mth, block.getInstructions(), insns); } public static void remove(MethodNode mth, BlockNode block, int index) { 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 97ec70a1c..d76c86779 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -181,7 +181,7 @@ public class InsnUtils { } @Nullable - public static InsnNode checkInsnType(InsnNode insn, InsnType insnType) { + public static InsnNode checkInsnType(@Nullable InsnNode insn, InsnType insnType) { if (insn != null && insn.getType() == insnType) { return insn; } diff --git a/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxRuntimeException.java b/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxRuntimeException.java index ced9878ae..eb56d010c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxRuntimeException.java +++ b/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxRuntimeException.java @@ -1,5 +1,11 @@ package jadx.core.utils.exceptions; +import java.util.Arrays; + +import jadx.core.utils.Utils; + +import static jadx.core.codegen.CodeWriter.NL; + public class JadxRuntimeException extends RuntimeException { private static final long serialVersionUID = -7410848445429898248L; @@ -8,6 +14,10 @@ public class JadxRuntimeException extends RuntimeException { super(message); } + public JadxRuntimeException(String... lines) { + super(Utils.listToString(Arrays.asList(lines), NL + " ")); + } + public JadxRuntimeException(String message, Throwable cause) { super(message, cause); } From a7f315f596c6850c680711282e519f91b8ca5468 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 26 Apr 2020 20:36:27 +0100 Subject: [PATCH 17/55] fix: split CONST used in PHI to help type inference (#900) --- .../jadx/core/dex/instructions/PhiInsn.java | 15 ++++ .../typeinference/TypeInferenceVisitor.java | 90 +++++++++++++++---- .../main/java/jadx/core/utils/BlockUtils.java | 13 +++ .../integration/types/TestTypeResolver14.java | 38 ++++++++ .../test/smali/types/TestTypeResolver14.smali | 63 +++++++++++++ 5 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver14.java create mode 100644 jadx-core/src/test/smali/types/TestTypeResolver14.smali diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java index caf4ede54..d3736b795 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/PhiInsn.java @@ -10,6 +10,7 @@ import jadx.core.dex.attributes.AFlag; 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.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.utils.Utils; @@ -83,6 +84,20 @@ public final class PhiInsn extends InsnNode { return reg; } + @Nullable + public RegisterArg getArgBySsaVar(SSAVar ssaVar) { + if (getArgsCount() == 0) { + return null; + } + for (InsnArg insnArg : getArguments()) { + RegisterArg reg = (RegisterArg) insnArg; + if (reg.getSVar() == ssaVar) { + return reg; + } + } + return null; + } + @Override public boolean replaceArg(InsnArg from, InsnArg to) { if (!(from instanceof RegisterArg) || !(to instanceof RegisterArg)) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 7979a3e24..87ec0b3ec 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -43,6 +43,7 @@ import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.blocksmaker.BlockSplitter; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.utils.BlockUtils; +import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; @JadxVisitor( @@ -74,29 +75,26 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (Consts.DEBUG) { LOG.info("Start type inference in method: {}", mth); } - boolean resolved = runTypePropagation(mth); - if (!resolved) { - boolean moveAdded = false; - for (SSAVar var : new ArrayList<>(mth.getSVars())) { - if (tryInsertAdditionalInsn(mth, var)) { - moveAdded = true; - } - } - if (moveAdded) { - InitCodeVariables.rerun(mth); - resolved = runTypePropagation(mth); - } - if (!resolved) { - resolved = runMultiVariableSearch(mth); - } - } - if (resolved) { + if (resolveTypes(mth)) { for (SSAVar var : new ArrayList<>(mth.getSVars())) { processIncompatiblePrimitives(mth, var); } } } + private boolean resolveTypes(MethodNode mth) { + if (runTypePropagation(mth)) { + return true; + } + if (trySplitConstInsns(mth)) { + return true; + } + if (tryInsertAdditionalMove(mth)) { + return true; + } + return runMultiVariableSearch(mth); + } + /** * Guess type from usage and try to set it to current variable * and all connected instructions with {@link TypeUpdate#apply(SSAVar, ArgType)} @@ -354,6 +352,64 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return false; } + private boolean trySplitConstInsns(MethodNode mth) { + boolean constSplitted = false; + for (SSAVar var : new ArrayList<>(mth.getSVars())) { + if (checkAndSplitConstInsn(mth, var)) { + constSplitted = true; + } + } + if (!constSplitted) { + return false; + } + InitCodeVariables.rerun(mth); + return runTypePropagation(mth); + } + + private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) { + if (var.getUsedInPhi().size() < 2) { + return false; + } + InsnNode assignInsn = var.getAssign().getAssignInsn(); + InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST); + if (constInsn == null) { + return false; + } + BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn); + if (blockNode == null) { + return false; + } + // for every PHI make separate CONST insn + boolean first = true; + for (PhiInsn phiInsn : var.getUsedInPhi()) { + if (first) { + first = false; + continue; + } + InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth); + copyInsn.add(AFlag.SYNTHETIC); + BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn); + + RegisterArg phiArg = phiInsn.getArgBySsaVar(var); + phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate()); + } + return true; + } + + private boolean tryInsertAdditionalMove(MethodNode mth) { + boolean moveAdded = false; + for (SSAVar var : new ArrayList<>(mth.getSVars())) { + if (tryInsertAdditionalInsn(mth, var)) { + moveAdded = true; + } + } + if (!moveAdded) { + return false; + } + InitCodeVariables.rerun(mth); + return runTypePropagation(mth); + } + /** * Add MOVE instruction before PHI in bound blocks to make 'soft' type link. * This allows to use different types in blocks merged by PHI. 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 d076e03a7..92f9e6aab 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -663,6 +663,19 @@ public class BlockUtils { return false; } + public static boolean insertAfterInsn(BlockNode block, InsnNode insn, InsnNode newInsn) { + List instructions = block.getInstructions(); + int size = instructions.size(); + for (int i = 0; i < size; i++) { + InsnNode instruction = instructions.get(i); + if (instruction == insn) { + instructions.add(i + 1, newInsn); + return true; + } + } + return false; + } + public static boolean replaceInsn(MethodNode mth, InsnNode oldInsn, InsnNode newInsn) { for (BlockNode block : mth.getBasicBlocks()) { if (replaceInsn(mth, block, oldInsn, newInsn)) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver14.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver14.java new file mode 100644 index 000000000..df8e69055 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver14.java @@ -0,0 +1,38 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestTypeResolver14 extends SmaliTest { + // @formatter:off + /* + public Date test() throws Exception { + Date date = null; + Long l = null; + Cursor query = DBUtil.query(false, (CancellationSignal) null); + try { + if (query.moveToFirst()) { + if (!query.isNull(0)) { + l = Long.valueOf(query.getLong(0)); + } + date = this.this$0.toDate(l); + } + return date; + } finally { + query.close(); + } + } + */ + // @formatter:on + + @Test + public void test() { + disableCompilation(); + assertThat(getClassNodeFromSmali()) + .code() + .doesNotContain("? r2"); + } +} diff --git a/jadx-core/src/test/smali/types/TestTypeResolver14.smali b/jadx-core/src/test/smali/types/TestTypeResolver14.smali new file mode 100644 index 000000000..b702f7a1c --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver14.smali @@ -0,0 +1,63 @@ +.class public Ltypes/TestTypeResolver14; +.super Ljava/lang/Object; +.source "SourceFile" + +.method public test()Ljava/util/Date; + .registers 5 + .annotation system Ldalvik/annotation/Throws; + value = { + Ljava/lang/Exception; + } + .end annotation + + .line 472 + const/4 v2, 0x0 + const/4 v3, 0x0 + + invoke-static {v3, v2}, Landroidx/room/util/DBUtil;->query(ZLandroid/os/CancellationSignal;)Landroid/database/Cursor; + move-result-object v0 + + .line 475 + :try_start_e + invoke-interface {v0}, Landroid/database/Cursor;->moveToFirst()Z + move-result v1 + + if-eqz v1, :cond_2d + + .line 477 + invoke-interface {v0, v3}, Landroid/database/Cursor;->isNull(I)Z + move-result v1 + + if-eqz v1, :cond_1b + + goto :goto_23 + + .line 480 + :cond_1b + invoke-interface {v0, v3}, Landroid/database/Cursor;->getLong(I)J + move-result-wide v1 + + invoke-static {v1, v2}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long; + move-result-object v2 + + .line 482 + :goto_23 + iget-object v1, p0, Ltypes/TestTypeResolver14$8;->this$0:Ltypes/TestTypeResolver14; + + invoke-virtual {v1, v2}, Ltypeconverters/DateTypeConverter;->toDate(Ljava/lang/Long;)Ljava/util/Date; + move-result-object v2 + + :try_end_2d + .catchall {:try_start_e .. :try_end_2d} :catchall_31 + + .line 488 + :cond_2d + invoke-interface {v0}, Landroid/database/Cursor;->close()V + return-object v2 + + :catchall_31 + move-exception v1 + invoke-interface {v0}, Landroid/database/Cursor;->close()V + .line 489 + throw v1 +.end method From 7f5092c0d59604dca757f76afaa748fa1af41628 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 27 Apr 2020 22:10:21 +0100 Subject: [PATCH 18/55] fix: redone shadowed fields handling (#897) --- jadx-core/src/main/java/jadx/core/Jadx.java | 4 +- .../dex/visitors/FindSuperUsageVisitor.java | 49 ----- .../core/dex/visitors/ShadowFieldVisitor.java | 181 ++++++++++++++++++ .../others/TestShadowingSuperMember.java | 50 +++-- .../others/TestShadowingSuperMember/A.smali | 16 -- .../others/TestShadowingSuperMember/B.smali | 25 --- .../others/TestShadowingSuperMember/C.smali | 12 -- 7 files changed, 211 insertions(+), 126 deletions(-) delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/FindSuperUsageVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/ShadowFieldVisitor.java delete mode 100644 jadx-core/src/test/smali/others/TestShadowingSuperMember/A.smali delete mode 100644 jadx-core/src/test/smali/others/TestShadowingSuperMember/B.smali delete mode 100644 jadx-core/src/test/smali/others/TestShadowingSuperMember/C.smali diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 1a05c5986..068436ea6 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -49,13 +49,10 @@ public class Jadx { if (args.isDebugInfo()) { passes.add(new DebugInfoParseVisitor()); } - - passes.add(new FindSuperUsageVisitor()); passes.add(new BlockSplitter()); if (args.isRawCFGOutput()) { passes.add(DotGraphVisitor.dumpRaw()); } - passes.add(new BlockProcessor()); passes.add(new BlockExceptionHandler()); passes.add(new BlockFinish()); @@ -72,6 +69,7 @@ public class Jadx { } passes.add(new GenericTypesVisitor()); + passes.add(new ShadowFieldVisitor()); passes.add(new DeboxingVisitor()); passes.add(new ModVisitor()); passes.add(new CodeShrinkVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/FindSuperUsageVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/FindSuperUsageVisitor.java deleted file mode 100644 index fe8a63220..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/FindSuperUsageVisitor.java +++ /dev/null @@ -1,49 +0,0 @@ -package jadx.core.dex.visitors; - -import jadx.core.dex.attributes.AFlag; -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; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; -import jadx.core.utils.exceptions.JadxException; - -@JadxVisitor( - name = "FindSuperUsageVisitor", - desc = "Finds variables where a member of the super class is used and marks them.", - runBefore = BlockSplitter.class -) -public class FindSuperUsageVisitor extends AbstractVisitor { - - @Override - public void visit(MethodNode mth) throws JadxException { - if (mth.isNoCode()) { - return; - } - process(mth); - } - - private static void process(MethodNode methodNode) { - ArgType superClass = methodNode.getParentClass().getSuperClass(); - if (superClass == null) { - return; - } - String superClassName = superClass.getObject(); - if (superClassName.equals("java.lang.Object")) { - return; - } - for (InsnNode instruction : methodNode.getInstructions()) { - if (instruction != null) { - for (InsnArg argument : instruction.getArguments()) { - if (argument.isRegister()) { - ArgType argumentType = ((RegisterArg) argument).getInitType(); - if (argumentType.isObject() && argumentType.getObject().equals(superClassName)) { - argument.add(AFlag.SUPER); - } - } - } - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ShadowFieldVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ShadowFieldVisitor.java new file mode 100644 index 000000000..d4d4e0e67 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ShadowFieldVisitor.java @@ -0,0 +1,181 @@ +package jadx.core.dex.visitors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.instructions.IndexInsnNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; +import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; +import jadx.core.utils.exceptions.JadxException; + +@JadxVisitor( + name = "ShadowFieldVisitor", + desc = "Fix shadowed field access", + runAfter = TypeInferenceVisitor.class, + runBefore = CodeShrinkVisitor.class +) +public class ShadowFieldVisitor extends AbstractVisitor { + private Map fixInfoMap; + + @Override + public void init(RootNode root) { + Map map = new HashMap<>(); + for (ClassNode cls : root.getClasses(true)) { + Map fieldFixMap = searchShadowedFields(cls); + if (!fieldFixMap.isEmpty()) { + FieldFixInfo fixInfo = new FieldFixInfo(); + fixInfo.fieldFixMap = fieldFixMap; + map.put(cls.getRawName(), fixInfo); + } + } + this.fixInfoMap = map; + } + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + fixShadowFieldAccess(mth, fixInfoMap); + } + + private static class FieldFixInfo { + Map fieldFixMap; + } + + private enum FieldFixType { + SUPER, + CAST + } + + private static Map searchShadowedFields(ClassNode thisCls) { + List allFields = collectAllInstanceFields(thisCls); + if (allFields.isEmpty()) { + return Collections.emptyMap(); + } + Map> mapByName = groupByName(allFields); + mapByName.entrySet().removeIf(entry -> entry.getValue().size() == 1); + if (mapByName.isEmpty()) { + return Collections.emptyMap(); + } + Map fixMap = new HashMap<>(); + for (List fields : mapByName.values()) { + boolean fromThisCls = fields.get(0).getParentClass() == thisCls; + if (fromThisCls && fields.size() == 2) { + // only one super class contains same field => can use super + FieldNode otherField = fields.get(1); + if (otherField.getParentClass() != thisCls) { + fixMap.put(otherField.getFieldInfo(), FieldFixType.SUPER); + } + } else { + // several super classes contains same field => can't use super, need cast to exact class + for (FieldNode field : fields) { + if (field.getParentClass() != thisCls) { + fixMap.put(field.getFieldInfo(), FieldFixType.CAST); + } + } + } + } + return fixMap; + } + + private static Map> groupByName(List allFields) { + Map> groupByName = new HashMap<>(allFields.size()); + for (FieldNode field : allFields) { + groupByName + .computeIfAbsent(field.getName(), k -> new ArrayList<>()) + .add(field); + } + return groupByName; + } + + private static List collectAllInstanceFields(ClassNode cls) { + List fieldsList = new ArrayList<>(); + ClassNode currentClass = cls; + while (currentClass != null) { + for (FieldNode field : currentClass.getFields()) { + if (!field.getAccessFlags().isStatic()) { + fieldsList.add(field); + } + } + ArgType superClass = currentClass.getSuperClass(); + if (superClass == null) { + break; + } + currentClass = cls.root().resolveClass(superClass); + } + return fieldsList; + } + + private static void fixShadowFieldAccess(MethodNode mth, Map fixInfoMap) { + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + processInsn(mth, insn, fixInfoMap); + } + } + } + + private static void processInsn(MethodNode mth, InsnNode insn, Map fixInfoMap) { + FieldInfo fieldInfo = getFieldInfo(insn); + if (fieldInfo == null) { + return; + } + InsnArg arg = insn.getArg(insn.getArgsCount() - 1); + ArgType type = arg.getType(); + if (!type.isTypeKnown() || !type.isObject()) { + return; + } + FieldFixInfo fieldFixInfo = fixInfoMap.get(type.getObject()); + if (fieldFixInfo == null) { + return; + } + FieldFixType fieldFixType = fieldFixInfo.fieldFixMap.get(fieldInfo); + if (fieldFixType == null) { + return; + } + fixFieldAccess(mth, fieldInfo, fieldFixType, arg); + } + + @Nullable + private static FieldInfo getFieldInfo(InsnNode insn) { + switch (insn.getType()) { + case IPUT: + case IGET: + return ((FieldInfo) ((IndexInsnNode) insn).getIndex()); + default: + return null; + } + } + + private static void fixFieldAccess(MethodNode mth, FieldInfo fieldInfo, FieldFixType fieldFixType, InsnArg arg) { + if (fieldFixType == FieldFixType.SUPER) { + if (arg.isThis()) { + // convert 'this' to 'super' + arg.add(AFlag.SUPER); + return; + } + } + // apply cast + InsnNode castInsn = new IndexInsnNode(InsnType.CAST, fieldInfo.getDeclClass().getType(), 1); + castInsn.addArg(arg.duplicate()); + castInsn.add(AFlag.SYNTHETIC); + castInsn.add(AFlag.EXPLICIT_CAST); + arg.wrapInstruction(mth, castInsn, false); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestShadowingSuperMember.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestShadowingSuperMember.java index 22aaf374d..f09fcd46c 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestShadowingSuperMember.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestShadowingSuperMember.java @@ -2,46 +2,54 @@ package jadx.tests.integration.others; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; -import jadx.tests.api.SmaliTest; +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 TestShadowingSuperMember extends SmaliTest { - // @formatter:off - /* - - public class C { +public class TestShadowingSuperMember extends IntegrationTest { + public static class TestCls { + public static class C { public C(String s) { } } - public class A { - public int A00; + public static class A { + public int a00; + public A(String s) { } } - public class B extends A { - public C A00; + public static class B extends A { + public C a00; + public B(String str) { super(str); } public int add(int b) { - return super.A00 + b; + return super.a00 + b; + } + + public int sub(int b) { + return ((A) this).a00 - b; } } - */ - // @formatter:on + + public void check() { + B b = new B(""); + ((A) b).a00 = 2; + assertThat(b.add(3)).isEqualTo(5); + assertThat(b.sub(3)).isEqualTo(-1); + } + } @Test public void test() { - allowWarnInCode(); - ClassNode cls = getClassNodeFromSmaliFiles("B"); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return super.A00 + ")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return super.a00 + b;") + .containsOne("return super.a00 - b;") + .containsOne("((A) b).a00 = 2;"); } } diff --git a/jadx-core/src/test/smali/others/TestShadowingSuperMember/A.smali b/jadx-core/src/test/smali/others/TestShadowingSuperMember/A.smali deleted file mode 100644 index dee54405e..000000000 --- a/jadx-core/src/test/smali/others/TestShadowingSuperMember/A.smali +++ /dev/null @@ -1,16 +0,0 @@ -.class public Lothers/A; -.super Ljava/lang/Object; - -# instance fields -.field public A00:I - -# direct methods -.method public constructor (Ljava/lang/String;)V - .registers 3 - - .prologue - - invoke-direct {p0}, Ljava/lang/Object;->()V - - return-void -.end method diff --git a/jadx-core/src/test/smali/others/TestShadowingSuperMember/B.smali b/jadx-core/src/test/smali/others/TestShadowingSuperMember/B.smali deleted file mode 100644 index ddcaa6a6c..000000000 --- a/jadx-core/src/test/smali/others/TestShadowingSuperMember/B.smali +++ /dev/null @@ -1,25 +0,0 @@ -.class public Lothers/B; -.super Lothers/A; - -.field public A00:Lothers/C; - -# direct methods -.method public constructor (Ljava/lang/String;)V - .registers 3 - - .prologue - invoke-direct {p0, p1}, Lothers/A;->(Ljava/lang/String;)V - - return-void -.end method - - -.method public add(I)I - .registers 3 - - iget v1, p0, Lothers/A;->A00:I - - add-int/2addr v1, p1 - - return v1 -.end method diff --git a/jadx-core/src/test/smali/others/TestShadowingSuperMember/C.smali b/jadx-core/src/test/smali/others/TestShadowingSuperMember/C.smali deleted file mode 100644 index 98f21ed6e..000000000 --- a/jadx-core/src/test/smali/others/TestShadowingSuperMember/C.smali +++ /dev/null @@ -1,12 +0,0 @@ -.class public Lothers/C; -.super Ljava/lang/Object; - -.method public constructor (Ljava/lang/String;)V - .registers 3 - - .prologue - - invoke-direct {p0}, Ljava/lang/Object;->()V - - return-void -.end method From 258ecad277d390dd7916ebe02749288aa97477b5 Mon Sep 17 00:00:00 2001 From: Jan S Date: Wed, 29 Apr 2020 20:47:20 +0200 Subject: [PATCH 19/55] fix(res): XML parsing: handling of TYPE_DYNAMIC_REFERENCE entries (#919, PR #923) --- .../main/java/jadx/core/xmlgen/ParserConstants.java | 12 ++++++++++++ .../java/jadx/core/xmlgen/entry/ValuesParser.java | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java b/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java index f1d6f8264..0f1b51827 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ParserConstants.java @@ -48,6 +48,18 @@ public class ParserConstants { protected static final int TYPE_DIMENSION = 0x05; // The 'data' holds a complex number encoding a fraction of a container. protected static final int TYPE_FRACTION = 0x06; + + /** + * The 'data' holds a dynamic reference, a reference to another resource table entry. + * See https://github.com/skylot/jadx/issues/919 + */ + protected static final int TYPE_DYNAMIC_REFERENCE = 0x07; + + /** + * According to the sources of apktool this type seem to be related to themes + * See https://github.com/skylot/jadx/issues/919 + */ + protected static final int TYPE_DYNAMIC_ATTRIBUTE = 0x08; // Beginning of integer flavors... protected static final int TYPE_FIRST_INT = 0x10; // The 'data' is a raw integer value of the form n..n. diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java index 56fe7dcfa..cdd9c73d2 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java @@ -100,6 +100,7 @@ public class ValuesParser extends ParserConstants { case TYPE_INT_COLOR_RGB4: return String.format("#%03x", data & 0xFFF); + case TYPE_DYNAMIC_REFERENCE: case TYPE_REFERENCE: { String ri = resMap.get(data); if (ri == null) { @@ -131,6 +132,9 @@ public class ValuesParser extends ParserConstants { return decodeComplex(data, false); case TYPE_FRACTION: return decodeComplex(data, true); + case TYPE_DYNAMIC_ATTRIBUTE: + LOG.warn("Data type TYPE_DYNAMIC_ATTRIBUTE not yet supported: {}", data); + return " TYPE_DYNAMIC_ATTRIBUTE: " + data; default: LOG.warn("Unknown data type: 0x{} {}", Integer.toHexString(dataType), data); From 2dce1c0ad920c4cc3f6aaa84cf3f2124dd97ad2d Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 3 May 2020 16:21:27 +0100 Subject: [PATCH 20/55] build: update gradle and dependencies --- build.gradle | 10 +++++----- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 58694 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 3 +++ jadx-core/build.gradle | 6 +++--- jadx-gui/build.gradle | 8 ++++---- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 991af3a57..a6e21c2a4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'org.sonarqube' version '2.8' - id 'com.github.ben-manes.versions' version '0.27.0' - id "com.diffplug.gradle.spotless" version "3.27.1" + id 'com.github.ben-manes.versions' version '0.28.0' + id "com.diffplug.gradle.spotless" version "3.28.1" } ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" @@ -37,11 +37,11 @@ allprojects { testCompile 'ch.qos.logback:logback-classic:1.2.3' testCompile 'org.hamcrest:hamcrest-library:2.2' - testCompile 'org.mockito:mockito-core:3.2.4' + testCompile 'org.mockito:mockito-core:3.3.3' testCompile 'org.assertj:assertj-core:3.15.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 GIT binary patch delta 6577 zcmYkAbyQT*w}4>)kxq%Bq>+@Al)yql!t+#+E>53IV^;nKUp^h z4s3gkgN%3})P~|EIG7tA>p3fA-P09~3?!BA;4bImM)6XMVtxPCsNO*R8`BM+7JTT( z%DMK_X0u;^`W#m#Ec6g#cs0%#ER_VbZbDE;Xfo6SxH#Jk{G(@Ad9*Ni==)yN&+Rs+ z!c5TRmq9CHM7*0Q{Uj9E>5GhmX#~DLb;+ll z-!FDVFymGnKRbAxQ0Rzpxzf2^IIJZ1>a*fh3^K^l2iUjT$-gD*2u?zG!9_ig1Ulvk zVy#gFy&uq-r`L2o`taG$t$-ROOh@WB(V7|PSzLEhBel)=tr_h5q~-=lfBiIaG-@wk zBq3>qaP`ZEdoQnNbun7EP_R74YiH^8;&y3c`JXY2C}9eWD~SoPu(5u~BT-ou705&# z(j53;{6KX%ts|QD8 zmei!%J?bD0pGB6rrzF3Ql4*rgVKrN33Y||4vWuVRKs>deCPbA_CvjUl;RXEOrT4(m zxINRPIa9#uO~1D1Q#bsM9eukHf}6O{pGw;+ppWNgFcO`3yrOJ5y(f`P;lLa*;FbRM zB@6#w0+(7p)M&JU*^0=M55Aoo4{;;*yUD~nK0+Oa6Wk=2f3o#?BO2E}-q{g_3H_wg z0;-~+W22xve~yBJB8{@|3ve$aMM2@_LP2>6s|At4rllw#)_$CkVXs~Am0ogKD*|j_ zgiI6wW=_0?pQ`6cF%!hwoxE7)ja4t2s;W$!XAP>%4?b0uF*&iXt(lmnIlq5b)v-z5 z@o_CEs960G(Va2M1b+Ve&u{3Tt&W=wujzA1L{0!A;<4?7f{1J9D<+5sp{o0Gl5$Qh zvBaG^vwV&eGKy$?nc}Imhos%j6{jeAIh|0KF*kvI?($YQ(>(!ky77|cTSHMssfR~G z$!TD|WuAv}uxh9`c^b%!wg_oPRMgR?<4-nbn$pQN=jV~oM~!_>Yym71wP48|FE*y1 z96R%lnZ`e5kFBux^GVnme^+#ojZ%|>Xp;`YTt;t&7%2RdyYrDTqAOysp!;^Q-zL2m z{<3O67MM#{q;G@|kDYT#DpCIJl3H#GxYt0ge(`7+S_gDW^oSMNIwm;Zn$I<&Bf(q6 zXRfi^Ts7qA$iN`Y1fg>%(2}%hvhO1!6{>4Wyb#F1d4sm-*u{B+XkX)35({w=e9p@w z!Pg7I))TN#nc`rdU`tKl&M>kWI4ayM{EB@QRb%u*hp0?(Z|kK`q<%-Mn|Rk$Kry&x z=mbY6CaVbil`u$ZZ(N{TTq$+NqK_^ai;mb{lDg>40G|0=XRo2tJyC3p-5k}f^7?0m z!}f`0iJ$zgCO+DX83Hi1e4nescg=5HJKW77vKP%&cungqf-bJ@?y8f`cxo82Am4tdK5irHk!Zy(hjoC+G|8`B*GSSqK!XpB3>XX;C&&ThUp z(T{Z|%<&VjZseczWppu0qfOIq$Lpwg#xP`3*axm&594YRNEg^VdLLbql&Crh zxk@ZEo?micfn~+C=G#?x?rA~#u&fZ4B$0|oO=>5vz&Kr7CNNmEd3)%nX`0iU3>HC! zT?bwEC1;a$T-+#3;`a*P5!UkiVw=dO4u;bWwdE8VOW8ZCEPG&c8+TG;hC!Qi?L4?I zpC)lC*?uKaF3_iZ?^3Bi#f72TX`BY)$Sz@TFjGb|Zko819O%|kphiM-?J-}y*4>24 z1Z`uQG#^U(&XK9hTXJ7k*3IpxwO28-Dcqg~T2-zRcbnj>tQ;LXWH2x&vxfUL{jOGO z3G7epiCpEHPXb!vwOG}1y?}zf&~r@rl2pr0FJBLQe`Zx7xHwB+JF#v)zK?|P1iX%qe47=-$dP5eQmJLn)-7P*Q!|X_fg;{OP$8M}6aFDyBn9pp zAG@AQAIDED;?BF7i8eLnRcFHyi)s-y#2l}t%q{o~>R{|~BTF`M^WV@5Cp9RwF;YB6 z<;I-(^`&Co1awRat-Ba9hLnXWmjQi;b*q2AmBvwGJ*HLuGRtUGBr-<{d2^Hu9VCZ` zEmOQhVN;&3KEb$l;r&K7A0?lp9EmdU&B;|uK(khuYyBj6%w^jdc&x#vzIGg$3?Hm8 z@&DKtMcG{Syi=P=@)YSR&oIsVgN%b7)F$*IQZ&0Za*om#%Wi<02tTVqyF>I4B3MWt z$6TfNCMHLfuNPIvoPmrVvin(*Mh=UE#s_GL15-#6WAt#bomte?X~%J9PErp?aWm_n z6lC5s;l4)APgN^F#?aa2m|4Q`;UwvKYujR)bBgi{_!r2nF?gepca~A@k$Q-lOW9J@ zT}hH0!rO#xTxp@eRMm^NN=@IJWL+;(YROkv8}+tG!s*uW>Q8j@ z8yI`^Q1vgVB+2|UR@B92xet~aB{n8TyP3Tk_Fj3<8o;FK;@Z5{Gg>9^7N=Q;5{>05 z?gpL*2unrhmi!!Ns>5h4>9`#B4c;3@=pp;6=&OFGw$~@ z9Y6gX{2KFq*mUYB(M5GKeOJH@BzLxEN4wMMkP& zbZd=x`^V5OBR^aQz-jX^ef%>lW|0AxwHk&qir#mGAB{?bfHO#7H$G0T!6G}XdKt;y zZc@qt${l)haQ|wn=A!ggAy$%+4%53k(rxLsA&}pBq(uty$Hw|v1n#zDnlDow{`uwy zo?r@Fpm%qyWPIK<%_NqMdvJB27(^PubDrk?z-L){A^m{u86QAdaAxT90ECz$WCJ6n zw!gWlc$H2?+$z9N3dl3KMKwpMrnp}8;Y7i3`i`;qDdSj=Ub7ple;(*p=p?WsYhDg3 zYJl$CU0Oh>nn`x>?apggqu-0Hky~UJADVt4^=tRgQoMReTK!sFe)PN4;2&SS8W zGIaS8t1|V~wXlXvDc)Mdp3H+2z795??E|9^aaGeDdpnrjbPKoZ zuU~yQPN-*{EAb2vp4|}=+_3IxJNAm&8$2TmUQdCrI9x(IVpJ#HD?mg2%|wT(3@N?2Ch8K}NQP5-Veg)fb^46sXoW4y10LgLp>&pXJ6ZL0<68iSn68NFv#Q3fB)8gl>sZdbrt485)IyFEm9l=S*!Je&xWea7c*N9-;LD*Kr#-&UeRz zad>a;uZ=i4>lcMsZqbIIAu%E&t==)^#MxS(qUoWse#ukF6Z2v}ZSol;W&?|Jr131@ zMtl}@2kRk*DR%yZp#*&iupcJ%T`0^|^K< z3I^_?k9s2xUww#5&!)YD!Xecc4M}3rLqF0RvBrK9mpgStQ75;3?p1?R{i5ae?x(@3 z5aql@kOL)4FD`Z|xDw4M6bDPsa74e3@PO{?r)o|sL?4qN&>h;+w+pw+_f&AmIOMCW z@=p^Y>P7fDdt;J3Mv-(w{BI4b$NXWSAyevLFOMWsjUVo7OZLqE z*?ZdqiHo?-m%L}ZecB>T-1DR@5FI@@O3@KF$SI*Tt9QdyUJLLc^IGYcH7z-=n=C^p ziVaaw>_ zz6kp8%4Iy$Moa{Inys8lHMdLni*TK<>prSjVxnv`)1mFAkVe%5eiLIEY@WiQW7uRx z|K4S?+sOIa%WP2e>H_`-Lb-}_=>Kh$mu&oQmFwso2^JN-mA9J={gMk+Di>`!(|3!) z#Hd2HS|Q*;#&Hk_KQ*)Q$JCjusbivMi)FM^U3`4J*@J>(5cp4s;WO4 zaZ~J1_IHyYdhi4^y=X)|W4%8+6R#sv1(#$llI=pm)70JHa2&2*qNP*1qKmySp>KK+ zwoK}Im2^ODta_af$&3@pa8qp$cFcsRs8&z8d-^)98trqt2Y6j8mSu-5vS$gh_$Msk zjY2X6Jway6GlU@yCqLpytlFhFWmsr%+bqVRDxO_}=Q1ujX^9)jwG($`l%b}CID2~z zHSh=O<6IZOtQ9u`dzNl}&&)F-JW=q+c?G-SGSPAX>!(^s4d!~ZvX>K23UOk*%q41j zOgi_lA??Qm?ENX!6AVw({2ar%w^yA})k7D!GZwOR@_%>(&GGRq#1ScYGp+T~*v+Id z)1`{flq6+H#>V0k3=BNN?(I_)op!C8`i5sUSS8om(kV+`d6U_tD>jrttEYbUzCvT~*T815Plap2EGI3m6BGFADJWSzH2gNbXK zAMevc_gV`Hwqv_d6t2nD#8mRtLj}5u1A`p|zy^L7tn)2^#cmn5ttx>AzWu|}4319d zmTCBd3DG$iJAc12RQBtaqtaDO<(lhp)saUjc}ckOF-?*CILc)CHQ3-c&R_bIx^RC(Uh>H=?Hc!Jfq*uf^5pvZ1qUEjUGFLA48xlJ@Id&^o~ zAxnaPkQJ{5`miM|3u`!5Yl>vOG3{InE)J-^?GFBYhs^S3{f%XmmMDbY929%)tXDK^ z4&0msZpvP=Oj^{;CiXzs=(d5-Tj9y&vR~?%ulrK|3M7R8AoRPFd*Jh%S=Iyda9Ke_ zrF5}XI&XAA(WM2qY$-Iw=VH7%AroF4;p~b8;9td1F#2cg%y^x}8|g+T(nMU&Zr#zB z-RYWpGePM7mRPYj^xvwV5!U1{Qb-VxZQ=%)g%P$JAS;+A)+%LtlNZ;uSA+=6xC;W1 zZ&!}Qje-aZE$+yMeC&-WJLqg}I+P*%A{y4Qaq5y97gk+F4qy~fVTW7#R8qx7{kLj@ z_Ak&Hi`GnE(YIf+nBX>YuN&8z>0+n8Y4Mw_D`*=uT-^XHMD;CpOPj0`pX1G}5>QX= zPS1iRQ#%re7!OK%X6W0M^BrF0IHK`4^^7#J+x`8GKi86ZU=OWN9Rd zbc#BaTYr?doP4Q$Tbac6h=c1Tcuy;l?Gu<2wG$iKh^=kN1p-~6nuHE#vN&}$>STjm zpd>NS?sZTc`Yti+^Jx(&e|e>jw51=3B!N5zF}}Z+dmjmLgD^?|K2t{vCP(Y5cxl45 z^#&!362V;(_~IFmEp7G&NyG+08Lf|URTC2r&e;9YS?LAO`7_Iiod$D!uB3}mMv5NZLM!7V8_tEyUwc&kFa1isI?26Eogw$4lsNRB(#c3Ssm(>CFP`< zuem=>#4!%PU48QZO*F)iwJsf#~c=|+1W5feb` z44pz7si?Qj-K8bF6sL7&%FICc1M1vBmTxRa~P2hdeYJpZ#955J&b zqeVyms=gR(%w^R?^1A&w#Ap@G%}hbE=bp6}sf~VMdpZjHb}bxykA59XXKm?+-Sd~% z;Xw}ENaem6xp{yUqkQ@z^x;+Il6-@d59N}XiYXGL6;QWzd#QUz8R&)Ql$)Ph=q4%t z2Unt^=Ru1Mji9_%K^h15uS`f6VVOTS&b2=_dU&nt%RSrsMUY+vWcC91ej!2YKzLFi z7o|5#RqpAxW)fo!>%GSC=QWq}-chx2_7Cw$HaRJ14sv$m%L#iajDtdxcqEnql!qgs1EZuI-bz*5EO zAWxzL1X}g$g^3JgM8S%;%wjN|95AK3o{Z`BBlLV(B_zdIva)EKP4Y8FOYwp;$Raw@wT4E<{pj3{hDai8KZje zcEuA-{d?JgLv!WnmKq5MyMEX52loR(6fdEA-RV<{G8H5Igxq1>w}%2S)_ju;wF_ZM z$7!A^lLCtCZdv033jL{f&eI>9ISF2x$~~6;tnOzYI*(I*?>+6ozHgn+iutW-50rn% ztIAoG0!guTBfvFW3Thg_WtLf?4+*6q61dY`qXbfO*(>@w!l|u3&BIZu84UE^j!yro z^oi)PjvWObd1M?(HjP?Hjc1s_HH?DvC)%cciIXHNQnqKY1Mg3}aOh6*=l4mzd4Txc zLVTFGo>@6$+loh+i-?qdkxJD?$#HzVN62jNChy z4YB@j$_b-hu>?T$VRfJvu%s0s0Ef{(lrq7C9j(X!@J;?lNnl2+?0`t?f7)S9^Q45Z zG6zDOr=jV;rzj)?wzFyiNCrKXu>VVcSOWr1JYl$A%&@I}YQk6lTl(}a3eog}xp;BF z2-ewA(_y0P;(%cL?=XaO+#VrrP#hBP1}@E>Nc z)4|rBGPfW9Y4aX6jC&IZkPLfLMi?Xv6E-?e2or%4;{NZwMIr3ae@SO35VpC=4w(A< zPw^v(VQ;tC0lm@xG)9oQ zxqJfxZgT&HB=QJh)Z2tGvcms=GiKqxqjKmdC2Q%Df@d50Zk!pNuo|L1uQJKl2yY)r#$r^WuYHGdz7S_A9cR|BBV!D#1L$+T24p8a>Pgr3$< MViXjGx&OBR0?kH%b^rhX delta 6547 zcmYkAbx_pNyT)OqL%K`4yFnJ0Ub0fbkcY00Ec`v8pw# zP1%=K=fTZQx1pfej+Ro3pZ{H+B$tvoY7*_j#twUpZpfOnC9Xc>mcgedjEy*!&BAw+ z!Pb8qzSx)i-geP%Y&mo93hXitf4u*5hTDllPosG z#)a_-^*6(UY8N`S7#Hmosbzg7Pl<;TElEZd0hEZc|TV zsfGsW_Cs|WF=Fk4&PWdE3~w?1)ajZRB`0|;a45l@mC9V@1@RVN@ykVBK8wj$z=wr@aDeA*lqRvbqEYcJ++2G(*rVbDu7M7;lVb@s zUpiabP+>}OT-jh)W+<}$*eWiZ!a{(GunZh*`?>0O^2Pop%YFQ-&u%m(0r8~z!-&?N zYn(_=J{6xvr3iEFhzT?{vM~CW%j8)1I6t@AfImYf>vJhH!Xrw5h_lkT}!v{y-23=jSt)Sxt`>B z(!Au<2-0p1MQWh`&bz(aR;aC0Ywui+>UmdxbpB&%mezJJ*n&xThv`}u!B~E(N6-K3 z3_8U>zN>1nxd(h1iZ4Rq7~R3ap1mtva6>is57nm3v~T=d4VC6NTP-$W3|T+EOHnOs z6tTAIq*mP>cz`uFr^&$b^x`)MujcOSgT=Yceij*Y2cU~z8-M<+1mERc*)H-}DR&(h zw?8L`cL$at6C$(3&N&zm$_4RI;qh@^|D<^Q1j)=%Hg<)&3a~S>T?6fn(Y2$jXta6S zO*-lYV;1+QIO#)S7L)%6kv;6q8ytk%rpw(R;ZohTbgfkyhu`}w@D}dQrJTkg$+${qm4m?HteM^(ho{20(c64>NjM2%I9G12_vO{<(vZQd zeYr)er=*_dY|4^hg-E$#nyQ03GpQ4-Q>6Mi+kNh?FK_xpfIl`MPV4Yy3cqmDKrpYQ zesF@i+ZSGz(@?*!1V@TSA=|@^9YkoSsgwI8i46HP#)kQLQx{t)nUusL!hR_fp_d86 zt6zUwGi1>GCU1(kw9Tn*Z*I4U?>Bm*Gn!a26D8kkO%asgWz9h?L?M`Aamwl&@P$p8 z-0z1ko0m^H#GcxW?8A@Qr~$iG<1%aA=Y(bR-G`#gEI$V!O^dX_dwmioj(5~kcZc}q z!j}a(&4VKAIw7#H5%M(h8rbr}@-_RxC5_YaHM%uX&ADKNdnWvcPF=7P{=yoTljgvk z6!VD4fE~l^=#+;87bGzasykginl9YLMr2J*O+NeCPMyo2Gra8fsqiQ`7s-BU8kRw} z=mQ^6!JW;kd*js3IK%X_n$F2?gnyPdmMz;<}hhX8vL8# zDwb%YeX5HF4~B8Zit^3_wRA8m_7pTF3j1!)mdP4XLSH2=$J-dPiqH6Dh@j@?CD;r` zR$IQ+WWpb>Xw^^DmRHcmN+#F^#-;d8?l%bvl|*4MN7OhV)mNH&72YV%wl(zBp+! zp{cou)D(g0n+xXCANKg!ER|_wPC>bx7-khT3EI#3PL)x9?_em_p`|iUe;3QW2p4Uc zv$CIRUL;gYhF`->`J<_bMn!l*UX&>W{xC7-XnRWc1|lH6m4ygrIo&mVs`>#Pb1v8>{GX-P4kK_KxSuyies;QBq1e->cP5+I;eAg9LbM^wtQ6eSW_zWF8 zI^>q<)j(@pva4?EE_PMo%gu%y`?E7d?e(WTWB>9&u`(yaalT)+pV9kcLPsL0KfV%u zc`H~JJ^Mh-J-BS0P}*69ouWEE<<9j7`A|5;d{M00Q6yV@At949h5jx_bv?(4%R{?J z_4E1c!gX?~p~<^gRf=g=E+_Vx$91C{%zJsH*EwHU74kDfi9elX)j7Vu%$osz1mq6S z+B0uR{A^U4QBOY9fAqYUmBU~EL2x~|c|3g-%f>aR(w}?1@Z7oGd`J3P^A-Ibj>6_w z{k0xhog3$NkbWcm+%+P{D8VWVW?dkh{@(R^1TWWEv_V^> zSaBI*x8WKK6-py7SIMl02$MS^6zBz{1@ z;bPeEOV*SwCmd}1zQ9Bt<1dP>ANcVrX`sqZ#Lctm56lic7SnjvsdF;>)i~)4)}6<8 zw>3kuJ6R?7lqCYM4+5leLIB{FKq@^Srr;_e9vKqp49!1e$Mo?uyV%V<^c}k0JY$e141jJkVTsm>WF? zzUm(myxyEf#<`GTnpaS5;b$-*bddR+=ipA45;OVx0Ci>}3ay2L1rZ&dWRo=voeU)U zukSaL`h57RPMmtbU6(#zA_lo?M$T~-&?rm`EIP1}2tL8<<{_<907tgqeEL3SsAI!k z2jgOUsW&{QL9N^1M$%VrXYb}SSI09g{%-q=@X+@NcaGE;Sk$ED=7Ox*;0*3Wi3^HW zfICY#b-$>~7%kFL&inoFFjq%+hvAJu*EQCjZXD-^tNyY(*JC&W!5tIGKI+i+N%gZY zSI5{_ZHY*1*6KBtgiF3f{Xo5ez5t)u!c$YO$IQpv|5==g7wqgwAyp*JJEs<+<#2Rb{s&@eV z;2pLXV}CIoejpWOF`HSeP>^@;wg--*snbwmz`h7Km33$+4sZ4=Hmpex-O zqJ1uQVCQliL8^Z2hc8r1pwrjeeG2L?3*AUK8hh7QV|M3XApI#FY-5`B0)FYsr+=TV zW?AHTHxy>#QbyO{Hb$0bq!##z*Ym!$b|RRW%<5ZHstN4rCK^^7pXU)ZD$diO;3SMm z-`5g7n|)S@A4GiKE1ec08xG$SOOPM=Ca1DfbRDca!_%7>sjyFiOWb;e>%9W&D$+?cLXYCh4ba##?-1<&69 zaH<~z9paWS)W!bcJ>&>%5zAt1xWSIIq5I>NE=@0mFzu$HKeDf>M`UydKzZyyx3FPV zeRI)5yX39+UAoH#@F)&0l$T-Q32(vjWcJ8eIYr*4HhHYu%Gzp;u^`rY^W9 z9F01NSn zDq+@Ud?UjbN4hEecEWu;zy1v)2|B(eJ@>Y7Tx@Gh>-?RsXZ|m`h$HcGdoCYKwmdKt z!(gspq5CDyr$8fzL?5HV6GmaPn2^yS@h89yg7P zv>kt>NjC;EWQ^Fk5ru=wy$FaZ-QCgW9%v=u{A~W?Tclu3=TMA6jUg>Q%z z0DZE&sp8FZymao0;o)X{%Kqin7mz{+-}O9v=eaHJm*EyfbIhlxL9)+En^Fen+s9N8 z?9Ax9wJ!8+3B12oy|Xcu{_u^c3VR%TaC=L%`u^wPqiI^v5FuzD97y?^zu;%?ANsX1Oib}xXjsN4^999+mULA4 zgAz^MtI5vp+<<&i@}JBu)`MW``uU|zgiw9nK(r^5AqHH64wH&)Qevoo`c(_9aG01@ zOK>GiZKeWSW2QnW&mnZ%&H5dtc^FZGo$L)1(otL-f>EU)oZoVaN*x-JV|xu-6Vyj&P0i{$#{T=~MwSw&I{A?F84i1gv( z)hRc=+_D2|mF=9Hi-23y=4-gvA3{SnYbVCzd5b9L(c9g?RP7|X zfs^d06B_u77gR!RA#r8+96}-`o@w!3Ua}0@QXG~eTeTy#G2yvRp$i%!$*HKZgl67s zu|>QhVci1yp>ajz$vxQsho-|ozQ!k%SwpGlrDD35d#FL5P0j9;aVK~M5V~R&*^=+L zSCzmzQciQYuf=0RCpt@)51vxm3rMU&y&##ir%NGZ&Zk(@TKmq)9z>pPm|7MW(fbxl zxZwmY; zN}{MPKvPp3B+<7pUV#b^t*{b12zyQPbh;WkjXCz}Ru>nJ#lDvm^~g+2m2&Ci#rf=W zlJ_Ne%V*;Dx(!}T2D|P6(VS$XM*iB2tVXeM6k^E?d+?5QXHqc1K{0n$%%*tB^=D>C z{Rv@&Y!C1X_)ss(h1eJ5{yqpOSSDRwxO1!itaD>RV1%dmf;F}BSF>z$+!ZNCm9>%3 zB$H}@JlE71f7KotsYWn%*}UuP-u5Lk4KCN2ahPFJs6v=g4a{r>xdoBi>Ku#l+Z>K= zwezjvKQ#3mdA(SahO=mcpI~JXIP!P>a*IrMJHz{yqYw^43@u);$e^P?Gl5N#L7VQX zb<;DDo;5P(0!j*-Ol}^`?3^Xd62%kK*S5*8(>qs@nJ8z%hMxE6519pfM|vn27qDE} zaJ>x&>A|+9=<^>R+%%8!d%3@~L?_MoFch9k8I9>)gNs0!m?%lJ@1~%hFpIc)ymh0K zd|UJS+{$Q#W+iY{stH?!&L(ymcFmPp%e!D^=o;<%1)qad$Ec-kK<%kdOG^}6NJy$G z)-+x^HXfcue(T86JkI|61%F15!*t1QUQa~Zk?9V@%;2+9n1|TEn<#9XV56}1AgZXl zEh`qo?!^}YIboKsV&BnqLav{2(1Y+83WbvGuyYYPD9q+)<7S|B zv-f*t`|zOOR4wEft=PL?k(rp6xJk;UDDyB{zVT`P3c`{8>*$4wl)kAd6io(Cm^}aF z@C!An4E3sss?9XD7k6BLFka4g)>Tcp@K(zv^>w~9bj{;Xq`%KV|84fFZ+^RDD5 z&D||R7u@IaMNW;>*F1*|X9|Zd_bnyKvu5EamB_jG`JPsUj_cXtfG9+Gjipd&=k*=@ zSAhOH1m8eW(icWXDUj9~ZfM}7GM$VC!a9aC-m z$9&}vXeQ@XN!yio)>wnSzdn=;q=i?)3mhg93pVMVBsjb;$m27x6+9D7HHXZ%-ySdS z%3-ymPnpOtY1D7si5fq6BpxnqYV$BGQ`pqmw2tS?7BLGj=p*uFAyE(xmF>T8^XMzz zw6z-2|HajrqxK4b-%h7+T@usb1> z->hmpIo^MR&k=ug(hd`I0w7tJq^B~q6snow@@qlwFrL0U_=9red9nQV!BLB*n%au_ z7SnFMfboKV`|!#-oxrN~aRU2-@%*wMv2nra9iSwbJ^W%l?!oMq_Pzy9gWK=ig7*ih zB4=|XT0P7ng?xD0PG3&1^@!%hf88|Yw;)fv9#>!EWu<)Ax(s=2e1TwHbCi+=oj+08 zYBbA9IG4oN*_Z#e$jD{DF%?^1`f9_>PM~~3ITW_pk)`WtDBgMk1&kTF^j1$1=|$tJ zjtNrAbC8($17KUyjjj)^@<#sc>1}DWs&?n>sE4Im$OpCZ^NIkktFI`#ivyY!GJ81& z3AJgh3$7e@uki@7pOuM3VcMnN-@w(jd&ay>k_L(%yKLOfHOtmDSNr6C3u$I%N$SQHW%=$FPV6i$Fz%`f zvTF|4kS7dRnJ>42(TDsLqaLY5@&Ey0u$q}4o#Y||v|WUqL1NK1mLOKneC`^BVDKV^ z+z6G7-OEnW<=4(hE4U}46Ng}{OS8|)el0=}!}g3YXD{bM1NRr-cDVaKP2}q4tH-0Q zC<%qSM}j(pfkZIce@5`Y*LfrC|DAIJGz*rXAcKFC&T0cZAY*|G#AE!=%EIu0!v#4I z0qlP)2{5=q2-q)DgFaaQLoL>H|4@+~A@1Mt>A#i#J{8zlgn^K7U~`cc7=b?pFy{#Y z&n0TqQy^hU8>HsmB*F;s{;wwP zuzw*uj2c*3KQ=Lj=5I&{G_6sCC_nz&@Ow=QG?@5LzFAj7 zy#Q*~;h 9 (53) - compile 'org.ow2.asm:asm:7.3.1' - compile 'org.jetbrains:annotations:18.0.0' + compile 'org.ow2.asm:asm:8.0.1' + compile 'org.jetbrains:annotations:19.0.0' compile 'com.google.code.gson:gson:2.8.6' compile 'org.smali:baksmali:2.4.0' compile('org.smali:smali:2.4.0') { exclude group: 'com.google.guava' } - compile 'com.google.guava:guava:28.2-jre' + compile 'com.google.guava:guava:29.0-jre' testCompile 'org.apache.commons:commons-lang3:3.9' } diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 934cdda12..6946bdc50 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -8,16 +8,16 @@ dependencies { compile(project(":jadx-core")) compile(project(":jadx-cli")) - compile 'com.fifesoft:rsyntaxtextarea:3.0.8' + compile 'com.fifesoft:rsyntaxtextarea:3.1.1' compile files('libs/jfontchooser-1.0.5.jar') compile 'hu.kazocsaba:image-viewer:1.2.3' - compile 'org.apache.commons:commons-lang3:3.9' + compile 'org.apache.commons:commons-lang3:3.10' compile 'org.apache.commons:commons-text:1.8' - compile 'io.reactivex.rxjava2:rxjava:2.2.17' + compile 'io.reactivex.rxjava2:rxjava:2.2.19' compile "com.github.akarnokd:rxjava2-swing:0.3.7" - compile 'com.android.tools.build:apksig:3.5.3' + compile 'com.android.tools.build:apksig:3.6.3' } application { From f3cd4e38d764de87e8848425c653b717cfd3d487 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 3 May 2020 18:16:59 +0100 Subject: [PATCH 21/55] fix: check enum constructor content before removing (#922) --- .../main/java/jadx/core/codegen/ClassGen.java | 20 ++++++- .../dex/attributes/nodes/EnumClassAttr.java | 8 +-- .../attributes/nodes/SkipMethodArgsAttr.java | 4 ++ .../jadx/core/dex/visitors/EnumVisitor.java | 38 +++++++++--- .../tests/integration/enums/TestEnums2a.java | 60 +++++++++++++++++++ .../tests/integration/enums/TestEnums6.java | 46 ++++++++++++++ .../tests/integration/enums/TestEnums7.java | 41 +++++++++++++ 7 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2a.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums6.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums7.java 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 5044fa649..11d30f048 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -9,6 +9,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import org.jetbrains.annotations.Nullable; + import com.android.dx.rop.code.AccessFlags; import com.google.common.collect.Streams; @@ -21,6 +23,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.LineAttrNode; +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; @@ -420,12 +423,13 @@ public class ClassGen { EnumField f = it.next(); code.startLine(f.getField().getAlias()); ConstructorInsn constrInsn = f.getConstrInsn(); - if (constrInsn.getArgsCount() > f.getStartArg()) { + MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); + int skipCount = getEnumCtrSkipArgsCount(callMth); + if (constrInsn.getArgsCount() > skipCount) { if (igen == null) { igen = makeInsnGen(enumFields.getStaticMethod()); } - MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); - igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth); + igen.generateMethodArguments(code, constrInsn, 0, callMth); } if (f.getCls() != null) { code.add(' '); @@ -446,6 +450,16 @@ public class ClassGen { } } + private int getEnumCtrSkipArgsCount(@Nullable MethodNode callMth) { + if (callMth != null) { + SkipMethodArgsAttr skipArgsAttr = callMth.get(AType.SKIP_MTH_ARGS); + if (skipArgsAttr != null) { + return skipArgsAttr.getSkipCount(); + } + } + return 0; + } + private InsnGen makeInsnGen(MethodNode mth) { MethodGen mthGen = new MethodGen(this, mth); return new InsnGen(mthGen, false); 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 dd9f24c50..122d73ca4 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 @@ -14,13 +14,11 @@ public class EnumClassAttr implements IAttribute { public static class EnumField { private final FieldNode field; private final ConstructorInsn constrInsn; - private final int startArg; private ClassNode cls; - public EnumField(FieldNode field, ConstructorInsn co, int startArg) { + public EnumField(FieldNode field, ConstructorInsn co) { this.field = field; this.constrInsn = co; - this.startArg = startArg; } public FieldNode getField() { @@ -31,10 +29,6 @@ public class EnumClassAttr implements IAttribute { return constrInsn; } - public int getStartArg() { - return startArg; - } - public ClassNode getCls() { return cls; } 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 0580e4bd4..9709e6523 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 @@ -55,6 +55,10 @@ public class SkipMethodArgsAttr implements IAttribute { return skipArgs.get(argNum); } + public int getSkipCount() { + return skipArgs.cardinality(); + } + @Override public AType getType() { return AType.SKIP_MTH_ARGS; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index a5f8b428c..60889f9a6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -19,6 +19,7 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; +import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; @@ -287,8 +288,13 @@ public class EnumVisitor extends AbstractVisitor { if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { return null; } - int startArg = co.getArgsCount() == 1 ? 1 : 2; - return new EnumField(enumFieldNode, co, startArg); + MethodInfo callMth = co.getCallMth(); + MethodNode mth = cls.dex().resolveMethod(callMth); + if (mth == null) { + return null; + } + markArgsForSkip(mth); + return new EnumField(enumFieldNode, co); } @Nullable @@ -306,10 +312,7 @@ public class EnumVisitor extends AbstractVisitor { } private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) { - String enumConstructor = "(Ljava/lang/String;I)V"; - String enumConstructorAlt = "(Ljava/lang/String;)V"; String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType)); - FieldInfo valuesFieldInfo = valuesField.getFieldInfo(); // remove compiler generated methods @@ -319,12 +322,11 @@ public class EnumVisitor extends AbstractVisitor { continue; } String shortId = mi.getShortId(); - boolean isSynthetic = mth.getAccessFlags().isSynthetic(); - if (mi.isConstructor() && !isSynthetic) { - if (shortId.equals(enumConstructor) - || shortId.equals(enumConstructorAlt)) { + if (mi.isConstructor()) { + if (isDefaultConstructor(mth, shortId)) { mth.add(AFlag.DONT_GENERATE); } + markArgsForSkip(mth); } else if (shortId.equals(valuesMethod) || usesValuesField(mth, valuesFieldInfo) || simpleValueOfMth(mth, clsType)) { @@ -333,6 +335,24 @@ public class EnumVisitor extends AbstractVisitor { } } + private void markArgsForSkip(MethodNode mth) { + // skip first and second args + SkipMethodArgsAttr.skipArg(mth, 0); + if (mth.getMethodInfo().getArgsCount() > 1) { + SkipMethodArgsAttr.skipArg(mth, 1); + } + } + + private boolean isDefaultConstructor(MethodNode mth, String shortId) { + boolean defaultId = shortId.equals("(Ljava/lang/String;I)V") + || shortId.equals("(Ljava/lang/String;)V"); + if (defaultId) { + // check content + return mth.countInsns() == 0; + } + return false; + } + private boolean simpleValueOfMth(MethodNode mth, ArgType clsType) { InsnNode returnInsn = InsnUtils.searchSingleReturnInsn(mth, insn -> insn.getArgsCount() == 1); if (returnInsn == null) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2a.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2a.java new file mode 100644 index 000000000..7532850a8 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2a.java @@ -0,0 +1,60 @@ +package jadx.tests.integration.enums; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; +import static jadx.tests.integration.enums.TestEnums2a.TestCls.DoubleOperations.DIVIDE; +import static jadx.tests.integration.enums.TestEnums2a.TestCls.DoubleOperations.TIMES; + +public class TestEnums2a extends IntegrationTest { + + public static class TestCls { + + public interface IOps { + double apply(double x, double y); + } + + public enum DoubleOperations implements IOps { + TIMES("*") { + @Override + public double apply(double x, double y) { + return x * y; + } + }, + DIVIDE("/") { + @Override + public double apply(double x, double y) { + return x / y; + } + }; + + private final String op; + + DoubleOperations(String op) { + this.op = op; + } + + public String getOp() { + return op; + } + } + + public void check() { + assertThat(TIMES.getOp()).isEqualTo("*"); + assertThat(DIVIDE.getOp()).isEqualTo("/"); + + assertThat(TIMES.apply(2, 3)).isEqualTo(6); + assertThat(DIVIDE.apply(10, 5)).isEqualTo(2); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("TIMES(\"*\") {") + .containsOne("DIVIDE(\"/\")"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums6.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums6.java new file mode 100644 index 000000000..bc88c0437 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums6.java @@ -0,0 +1,46 @@ +package jadx.tests.integration.enums; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestEnums6 extends IntegrationTest { + + public static class TestCls { + public enum Numbers { + ZERO, + ONE(1); + + private final int n; + + Numbers() { + this(0); + } + + Numbers(int n) { + this.n = n; + } + + public int getN() { + return n; + } + } + + public void check() { + Assertions.assertThat(TestCls.Numbers.ZERO.getN()).isEqualTo(0); + Assertions.assertThat(TestCls.Numbers.ONE.getN()).isEqualTo(1); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("ZERO,") + .containsOne("Numbers() {") + .containsOne("ONE(1);"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums7.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums7.java new file mode 100644 index 000000000..8248e0c36 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums7.java @@ -0,0 +1,41 @@ +package jadx.tests.integration.enums; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestEnums7 extends IntegrationTest { + + public static class TestCls { + public enum Numbers { + ZERO, + ONE; + + private final int n; + + Numbers() { + this.n = this.name().equals("ZERO") ? 0 : 1; + } + + public int getN() { + return n; + } + } + + public void check() { + assertThat(Numbers.ZERO.getN()).isEqualTo(0); + assertThat(Numbers.ONE.getN()).isEqualTo(1); + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("ZERO,") + .containsOne("ONE;") + .containsOne("Numbers() {"); + } +} From 2207cd7b52a1f0b60dc0d83ceb298694d242485b Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 3 May 2020 19:27:54 +0100 Subject: [PATCH 22/55] fix: inline class constants to fix enum transform (#916) --- .gitignore | 1 + .../java/jadx/core/dex/visitors/ConstInlineVisitor.java | 6 ++++++ .../src/main/java/jadx/core/dex/visitors/EnumVisitor.java | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/.gitignore b/.gitignore index eb707b1f3..bd3ccf6ff 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # IntelliJ Idea files .idea/ +.run/ out/ *.iml *.ipr 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 a358b7628..89eef2dd4 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 @@ -100,6 +100,12 @@ public class ConstInlineVisitor extends AbstractVisitor { constArg = InsnArg.wrapArg(constGet); constArg.setType(ArgType.STRING); } + } else if (insnType == InsnType.CONST_CLASS) { + if (sVar.isUsedInPhi()) { + return; + } + constArg = InsnArg.wrapArg(insn.copyWithoutResult()); + constArg.setType(ArgType.CLASS); } else { return; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 60889f9a6..fd331d4b2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -293,6 +293,12 @@ public class EnumVisitor extends AbstractVisitor { if (mth == null) { return null; } + List regs = new ArrayList<>(); + co.getRegisterArgs(regs); + if (!regs.isEmpty()) { + cls.addWarnComment("Init of enum " + enumFieldNode.getName() + " can be incorrect"); + } + markArgsForSkip(mth); return new EnumField(enumFieldNode, co); } From 87852328daf4a14dfeef997543214f06d3bf31d8 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 4 May 2020 11:50:31 +0100 Subject: [PATCH 23/55] fix: resolve error in SkipMethodArgsAttr if method not yet loaded (#924) --- .../java/jadx/core/dex/attributes/nodes/SkipMethodArgsAttr.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9709e6523..a5d483cdf 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 @@ -44,7 +44,7 @@ public class SkipMethodArgsAttr implements IAttribute { private final BitSet skipArgs; private SkipMethodArgsAttr(MethodNode mth) { - this.skipArgs = new BitSet(mth.getArgRegs().size()); + this.skipArgs = new BitSet(mth.getMethodInfo().getArgsCount()); } public void skip(int argNum) { From 4e3d85887ca905956469688abaea576f6082e580 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 4 May 2020 14:55:11 +0100 Subject: [PATCH 24/55] fix: correctly process extended enums if class is not inner (#924) --- .../jadx/core/dex/visitors/EnumVisitor.java | 51 ++++++++++--------- .../java/jadx/core/utils/InsnRemover.java | 4 +- .../java/jadx/tests/api/IntegrationTest.java | 6 ++- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index fd331d4b2..2a81bfa0c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -45,7 +45,6 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.BlockInsnPair; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; -import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxException; import static jadx.core.utils.InsnUtils.checkInsnType; @@ -170,18 +169,9 @@ public class EnumVisitor extends AbstractVisitor { && cls.root().getArgs().isRenameValid()) { fieldNode.getFieldInfo().setAlias(name); } - if (!co.getClassType().equals(cls.getClassInfo())) { - // enum contains additional methods - for (ClassNode innerCls : cls.getInnerClasses()) { - processEnumInnerCls(co, enumField, innerCls); - } - } fieldNode.add(AFlag.DONT_GENERATE); + processConstructorInsn(cls, enumField, classInitMth, staticBlock); } - - List constrInsns = Utils.collectionMap(attr.getFields(), EnumField::getConstrInsn); - InsnRemover.removeAllWithoutUnbind(staticBlock, constrInsns); - valuesField.add(AFlag.DONT_GENERATE); InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove); if (classInitMth.countInsns() == 0) { @@ -191,6 +181,28 @@ public class EnumVisitor extends AbstractVisitor { return true; } + private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, BlockNode staticBlock) { + ConstructorInsn co = enumField.getConstrInsn(); + ClassInfo enumClsInfo = co.getClassType(); + if (!enumClsInfo.equals(cls.getClassInfo())) { + ClassNode enumCls = cls.root().resolveClass(enumClsInfo); + if (enumCls != null) { + processEnumCls(enumField, enumCls); + cls.addInlinedClass(enumCls); + } + } + List regs = new ArrayList<>(); + co.getRegisterArgs(regs); + if (!regs.isEmpty()) { + cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect"); + } + MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + if (ctrMth != null) { + markArgsForSkip(ctrMth); + } + InsnRemover.removeWithoutUnbind(classInitMth, staticBlock, co); + } + private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) { FieldInfo searchField = valuesField.getFieldInfo(); for (BlockNode blockNode : classInitMth.getBasicBlocks()) { @@ -288,18 +300,10 @@ public class EnumVisitor extends AbstractVisitor { if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { return null; } - MethodInfo callMth = co.getCallMth(); - MethodNode mth = cls.dex().resolveMethod(callMth); - if (mth == null) { + MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + if (ctrMth == null) { return null; } - List regs = new ArrayList<>(); - co.getRegisterArgs(regs); - if (!regs.isEmpty()) { - cls.addWarnComment("Init of enum " + enumFieldNode.getName() + " can be incorrect"); - } - - markArgsForSkip(mth); return new EnumField(enumFieldNode, co); } @@ -378,10 +382,7 @@ public class EnumVisitor extends AbstractVisitor { return InsnUtils.searchInsn(mth, InsnType.SGET, insnTest) != null; } - private static void processEnumInnerCls(ConstructorInsn co, EnumField field, ClassNode innerCls) { - if (!innerCls.getClassInfo().equals(co.getClassType())) { - return; - } + private static void processEnumCls(EnumField field, ClassNode innerCls) { // remove constructor, because it is anonymous class for (MethodNode innerMth : innerCls.getMethods()) { if (innerMth.getAccessFlags().isConstructor()) { diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java index 623d4a1dc..d42bb5aa1 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java @@ -210,7 +210,9 @@ public class InsnRemover { return true; } } - mth.addWarnComment("Failed to remove instruction: " + insn + " from block: " + block); + if (!insn.contains(AFlag.WRAPPED)) { + mth.addWarnComment("Failed to remove instruction: " + insn + " from block: " + block); + } return false; } 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 ada2ea51c..004c8d581 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -38,6 +38,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.DebugChecks; +import jadx.core.utils.Utils; import jadx.core.utils.files.FileUtils; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; @@ -262,7 +263,10 @@ public abstract class IntegrationTest extends TestUtils { protected void checkCode(ClassNode cls) { assertFalse(hasErrors(cls), "Inconsistent cls: " + cls); for (MethodNode mthNode : cls.getMethods()) { - assertFalse(hasErrors(mthNode), "Method with problems: " + mthNode); + if (hasErrors(mthNode)) { + fail("Method with problems: " + mthNode + + "\n " + Utils.listToString(mthNode.getAttributesStringsList(), "\n ")); + } } assertThat(cls.getCode().toString(), not(containsString("inconsistent"))); } From 692536c584d36a3c9f2cae6ed218b60a8acb0372 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 4 May 2020 15:47:30 +0100 Subject: [PATCH 25/55] chore: ignore tests in tmp package --- .gitignore | 1 + jadx-core/build.gradle | 4 ++++ jadx-core/src/test/java/jadx/tests/external/.gitignore | 2 -- 3 files changed, 5 insertions(+), 2 deletions(-) delete mode 100644 jadx-core/src/test/java/jadx/tests/external/.gitignore diff --git a/.gitignore b/.gitignore index bd3ccf6ff..34d09155e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ node_modules/ jadx-output/ *-tmp/ +**/tmp/ *.dex *.class diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index 7c1bb960a..44ffadae7 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -16,3 +16,7 @@ dependencies { testCompile 'org.apache.commons:commons-lang3:3.9' } + +test { + exclude '**/tmp/*' +} diff --git a/jadx-core/src/test/java/jadx/tests/external/.gitignore b/jadx-core/src/test/java/jadx/tests/external/.gitignore deleted file mode 100644 index 8bdc5dc63..000000000 --- a/jadx-core/src/test/java/jadx/tests/external/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/ExternalTests.java -/*Tmp.java From 45b73046309a2e40728a1a82a74551ef43295795 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 8 May 2020 19:53:20 +0100 Subject: [PATCH 26/55] fix: workaround for link clicks in RSyntaxTextArea (#929) --- .../java/jadx/gui/ui/codearea/CodeArea.java | 22 +++++++- .../gui/ui/codearea/CodeLinkGenerator.java | 56 +++++++++++++------ 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index 8e75db620..867dae715 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -1,5 +1,9 @@ package jadx.gui.ui.codearea; +import java.awt.event.InputEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + import javax.swing.*; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; @@ -38,9 +42,21 @@ public final class CodeArea extends AbstractCodeArea { } setHyperlinksEnabled(true); - CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator(this); - setLinkGenerator(codeLinkProcessor); - addHyperlinkListener(codeLinkProcessor); + setLinkScanningMask(InputEvent.CTRL_DOWN_MASK); + CodeLinkGenerator codeLinkGenerator = new CodeLinkGenerator(this); + setLinkGenerator(codeLinkGenerator); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.isControlDown()) { + int offs = viewToModel(e.getPoint()); + JumpPosition jump = codeLinkGenerator.getJumpLinkAtOffset(CodeArea.this, offs); + if (jump != null) { + contentPanel.getTabbedPane().codeJump(jump); + } + } + } + }); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java index 44dcdc2cf..54e7233dd 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java @@ -3,52 +3,61 @@ package jadx.gui.ui.codearea; import java.util.Objects; import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; +import javax.swing.text.BadLocationException; import org.fife.ui.rsyntaxtextarea.LinkGenerator; import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Token; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.ContentPanel; import jadx.gui.utils.JumpPosition; -public class CodeLinkGenerator implements LinkGenerator, HyperlinkListener { +public class CodeLinkGenerator implements LinkGenerator { private static final Logger LOG = LoggerFactory.getLogger(CodeLinkGenerator.class); - private final ContentPanel contentPanel; private final CodeArea codeArea; private final JNode jNode; public CodeLinkGenerator(CodeArea codeArea) { - this.contentPanel = codeArea.getContentPanel(); this.codeArea = codeArea; this.jNode = codeArea.getNode(); } + @Nullable + public JumpPosition getJumpLinkAtOffset(RSyntaxTextArea textArea, int offset) { + try { + if (jNode.getCodeInfo() == null) { + return null; + } + int sourceOffset = getLinkSourceOffset(textArea, offset); + if (sourceOffset == -1) { + return null; + } + return getJumpBySourceOffset(textArea, sourceOffset); + } catch (Exception e) { + LOG.error("getJumpLinkAtOffset error", e); + return null; + } + } + @Override public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) { try { if (jNode.getCodeInfo() == null) { return null; } - Token token = textArea.modelToToken(offset); - int sourceOffset = codeArea.adjustOffsetForToken(token); + int sourceOffset = getLinkSourceOffset(textArea, offset); if (sourceOffset == -1) { return null; } - final JumpPosition defPos = codeArea.getDefPosForNodeAtOffset(sourceOffset); + JumpPosition defPos = getJumpBySourceOffset(textArea, sourceOffset); if (defPos == null) { return null; } - if (Objects.equals(defPos.getNode().getRootClass(), jNode) - && defPos.getLine() == textArea.getLineOfOffset(sourceOffset) + 1) { - // ignore self jump - return null; - } return new LinkGeneratorResult() { @Override public HyperlinkEvent execute() { @@ -67,11 +76,22 @@ public class CodeLinkGenerator implements LinkGenerator, HyperlinkListener { } } - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - Object obj = e.getSource(); - if (obj instanceof JumpPosition) { - contentPanel.getTabbedPane().codeJump((JumpPosition) obj); + private int getLinkSourceOffset(RSyntaxTextArea textArea, int offset) { + Token token = textArea.modelToToken(offset); + return codeArea.adjustOffsetForToken(token); + } + + @Nullable + private JumpPosition getJumpBySourceOffset(RSyntaxTextArea textArea, int sourceOffset) throws BadLocationException { + final JumpPosition defPos = codeArea.getDefPosForNodeAtOffset(sourceOffset); + if (defPos == null) { + return null; } + if (Objects.equals(defPos.getNode().getRootClass(), jNode) + && defPos.getLine() == textArea.getLineOfOffset(sourceOffset) + 1) { + // ignore self jump + return null; + } + return defPos; } } From e22474e0a70609d311a0abde4da7048979ef515b Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 9 May 2020 15:38:18 +0100 Subject: [PATCH 27/55] fix: inline move instructions to help process constructors (#927) --- jadx-core/src/main/java/jadx/core/Jadx.java | 1 + .../dex/instructions/args/RegisterArg.java | 10 +- .../java/jadx/core/dex/nodes/InsnNode.java | 11 ++ .../core/dex/visitors/ConstructorVisitor.java | 2 +- .../core/dex/visitors/MoveInlineVisitor.java | 103 ++++++++++++++++++ .../regions/variables/ProcessVariables.java | 20 ++++ .../invoke/TestConstructorWithMoves.java | 33 ++++++ .../invoke/TestConstructorWithMoves.smali | 19 ++++ 8 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/invoke/TestConstructorWithMoves.java create mode 100644 jadx-core/src/test/smali/invoke/TestConstructorWithMoves.smali diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 068436ea6..cf09e7dcd 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -58,6 +58,7 @@ public class Jadx { passes.add(new BlockFinish()); passes.add(new SSATransform()); + passes.add(new MoveInlineVisitor()); passes.add(new ConstructorVisitor()); passes.add(new InitCodeVariables()); passes.add(new MarkFinallyVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index ca9fe1ce9..f035224f0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -130,12 +130,20 @@ public class RegisterArg extends InsnArg implements Named { return duplicate(getRegNum(), sVar); } + public RegisterArg duplicate(ArgType initType) { + return duplicate(getRegNum(), initType, sVar); + } + public RegisterArg duplicate(@Nullable SSAVar ssaVar) { return duplicate(getRegNum(), ssaVar); } public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) { - RegisterArg dup = new RegisterArg(regNum, getInitType()); + return duplicate(regNum, getInitType(), sVar); + } + + public RegisterArg duplicate(int regNum, ArgType initType, @Nullable SSAVar sVar) { + RegisterArg dup = new RegisterArg(regNum, initType); if (sVar != null) { // only 'set' here, 'assign' or 'use' will binds later dup.setSVar(sVar); 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 98b3b7070..bd579e737 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 @@ -217,6 +217,17 @@ public class InsnNode extends LineAttrNode { } } + public boolean canRemoveResult() { + switch (getType()) { + case INVOKE: + case CONSTRUCTOR: + return true; + + default: + return false; + } + } + public boolean canReorder() { if (contains(AFlag.DONT_GENERATE)) { if (getType() == InsnType.MONITOR_EXIT) { 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 fe866cc0c..90593ca6f 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 @@ -24,7 +24,7 @@ import jadx.core.utils.InsnRemover; @JadxVisitor( name = "ConstructorVisitor", desc = "Replace invoke with constructor call", - runAfter = SSATransform.class, + runAfter = { SSATransform.class, MoveInlineVisitor.class }, runBefore = TypeInferenceVisitor.class ) public class ConstructorVisitor extends AbstractVisitor { 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 new file mode 100644 index 000000000..766faddf8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java @@ -0,0 +1,103 @@ +package jadx.core.dex.visitors; + +import java.util.ArrayList; +import java.util.List; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; +import jadx.core.dex.visitors.ssa.SSATransform; +import jadx.core.utils.InsnRemover; + +@JadxVisitor( + name = "MoveInlineVisitor", + desc = "Inline redundant move instructions", + runAfter = SSATransform.class, + runBefore = CodeShrinkVisitor.class +) +public class MoveInlineVisitor extends AbstractVisitor { + @Override + public void visit(MethodNode mth) { + if (mth.isNoCode()) { + return; + } + moveInline(mth); + } + + private static void moveInline(MethodNode mth) { + InsnRemover remover = new InsnRemover(mth); + for (BlockNode block : mth.getBasicBlocks()) { + remover.setBlock(block); + List insns = block.getInstructions(); + int size = insns.size(); + for (int i = 0; i < size; i++) { + InsnNode insn = insns.get(i); + if (insn.getType() == InsnType.MOVE + && processMove(mth, block, insn, i)) { + remover.addAndUnbind(insn); + } + } + remover.perform(); + } + } + + private static boolean processMove(MethodNode mth, BlockNode block, InsnNode move, int i) { + RegisterArg resultArg = move.getResult(); + InsnArg moveArg = move.getArg(0); + if (resultArg.sameRegAndSVar(moveArg)) { + return true; + } + SSAVar ssaVar = resultArg.getSVar(); + if (ssaVar.isUsedInPhi()) { + return false; + } + RegDebugInfoAttr debugInfo = resultArg.get(AType.REG_DEBUG_INFO); + for (RegisterArg useArg : ssaVar.getUseList()) { + InsnNode useInsn = useArg.getParentInsn(); + if (useInsn == null || !fromThisBlock(block, useInsn, i)) { + return false; + } + RegDebugInfoAttr debugInfoAttr = useArg.get(AType.REG_DEBUG_INFO); + if (debugInfoAttr != null) { + debugInfo = debugInfoAttr; + } + } + + // all checks passed, execute inline + for (RegisterArg useArg : new ArrayList<>(ssaVar.getUseList())) { + InsnNode useInsn = useArg.getParentInsn(); + InsnArg replaceArg; + if (moveArg.isRegister()) { + replaceArg = ((RegisterArg) moveArg).duplicate(useArg.getInitType()); + } else { + replaceArg = moveArg.duplicate(); + } + replaceArg.copyAttributesFrom(useArg); + if (debugInfo != null) { + replaceArg.addAttr(debugInfo); + } + if (useInsn == null || !useInsn.replaceArg(useArg, replaceArg)) { + mth.addWarnComment("Failed to replace arg in insn: " + useInsn); + } + } + return true; + } + + private static boolean fromThisBlock(BlockNode block, InsnNode insn, int curPos) { + List list = block.getInstructions(); + int size = list.size(); + for (int j = curPos; j < size; j++) { + if (list.get(j) == insn) { + return true; + } + } + return false; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java index a709995e7..d54efc374 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java @@ -26,6 +26,7 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.regions.AbstractRegionVisitor; import jadx.core.dex.visitors.regions.DepthRegionTraversal; import jadx.core.dex.visitors.typeinference.TypeCompare; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; @@ -41,6 +42,7 @@ public class ProcessVariables extends AbstractVisitor { if (mth.isNoCode() || mth.getSVars().isEmpty()) { return; } + removeUnusedResults(mth); List codeVars = collectCodeVars(mth); if (codeVars.isEmpty()) { @@ -64,6 +66,24 @@ public class ProcessVariables extends AbstractVisitor { } } + private static void removeUnusedResults(MethodNode mth) { + DepthRegionTraversal.traverse(mth, new AbstractRegionVisitor() { + @Override + public void processBlock(MethodNode mth, IBlock container) { + for (InsnNode insn : container.getInstructions()) { + RegisterArg resultArg = insn.getResult(); + if (resultArg != null) { + SSAVar ssaVar = resultArg.getSVar(); + if (ssaVar.getUseList().isEmpty() && insn.canRemoveResult()) { + insn.setResult(null); + mth.removeSVar(ssaVar); + } + } + } + } + }); + } + private void checkCodeVars(MethodNode mth, List codeVars) { int unknownTypesCount = 0; for (CodeVar codeVar : codeVars) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestConstructorWithMoves.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestConstructorWithMoves.java new file mode 100644 index 000000000..84ffe7138 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestConstructorWithMoves.java @@ -0,0 +1,33 @@ +package jadx.tests.integration.invoke; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestConstructorWithMoves extends SmaliTest { + // @formatter:off + /* + public boolean test() { + java.lang.Boolean r5 = new java.lang.Boolean + r8 = r5 + r5 = r8 + r6 = r8 + java.lang.String r7 = "test" + r6.(r7) + java.lang.Boolean r5 = (java.lang.Boolean) r5 + boolean r5 = r5.booleanValue() + r3 = r5 + return r3 + } + */ + // @formatter:on + + @Test + public void test() { + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("return new Boolean(\"test\").booleanValue();"); + } +} diff --git a/jadx-core/src/test/smali/invoke/TestConstructorWithMoves.smali b/jadx-core/src/test/smali/invoke/TestConstructorWithMoves.smali new file mode 100644 index 000000000..b001fbba2 --- /dev/null +++ b/jadx-core/src/test/smali/invoke/TestConstructorWithMoves.smali @@ -0,0 +1,19 @@ +.class public Linvoke/TestConstructorWithMoves; +.super Ljava/lang/Object; + +.method static public test()Z + .registers 11 + + new-instance v5, Ljava/lang/Boolean; + move-object v8, v5 + move-object v5, v8 + move-object v6, v8 + const-string v7, "test" + invoke-direct {v6, v7}, Ljava/lang/Boolean;->(Ljava/lang/String;)V + check-cast v5, Ljava/lang/Boolean; + invoke-virtual {v5}, Ljava/lang/Boolean;->booleanValue()Z + move-result v5 + move v3, v5 + return v3 +.end method + From b1d5ed0066a7b8a250f806d555d4145580d77dc7 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 9 May 2020 21:10:37 +0100 Subject: [PATCH 28/55] fix: don't modify method argument types in MethodInvokeVisitor (#927 #836) --- .../java/jadx/core/dex/nodes/MethodNode.java | 2 +- .../jadx/core/dex/nodes/utils/TypeUtils.java | 3 + .../dex/visitors/MethodInvokeVisitor.java | 49 ++++++++--- .../methods/MutableMethodDetails.java | 83 +++++++++++++++++++ 4 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java 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 6aea15ab7..ab595be06 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 @@ -181,7 +181,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, this.retType = mthInfo.getReturnType(); this.argTypes = mthInfo.getArgumentsTypes(); } else { - this.argTypes = types; + this.argTypes = Collections.unmodifiableList(types); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index 0bb936df0..76c83ad1d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -110,6 +110,9 @@ public class TypeUtils { @Nullable public ArgType replaceTypeVariablesUsingMap(ArgType replaceType, Map replaceMap) { + if (replaceMap.isEmpty()) { + return null; + } if (replaceType.isGenericType()) { return replaceMap.get(replaceType); } 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 d44559752..24c5eeae7 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 @@ -22,6 +22,7 @@ import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.methods.MutableMethodDetails; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.typeinference.TypeCompare; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; @@ -111,12 +112,19 @@ public class MethodInvokeVisitor extends AbstractVisitor { return; } - overloadMethods.add(mthDetails); - resolveTypeVariablesInMethodArgs(invokeInsn, mthDetails, overloadMethods); + // resolve generic type variables + Map typeVarsMapping = getTypeVarsMapping(invokeInsn); + IMethodDetails effectiveMthDetails = resolveTypeVars(mthDetails, typeVarsMapping); + List effectiveOverloadMethods = new ArrayList<>(overloadMethods.size() + 1); + for (IMethodDetails overloadMethod : overloadMethods) { + effectiveOverloadMethods.add(resolveTypeVars(overloadMethod, typeVarsMapping)); + } + effectiveOverloadMethods.add(effectiveMthDetails); + // search cast types to resolve overloading int argsOffset = invokeInsn.getFirstArgOffset(); List compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset); - List castTypes = searchCastTypes(parentMth, mthDetails, overloadMethods, compilerVarTypes); + List castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes); applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes); } @@ -147,8 +155,7 @@ public class MethodInvokeVisitor extends AbstractVisitor { return callMth.getDeclClass().getType(); } - private void resolveTypeVariablesInMethodArgs(BaseInvokeNode invokeInsn, IMethodDetails mthDetails, - List overloadedMethods) { + private Map getTypeVarsMapping(BaseInvokeNode invokeInsn) { MethodInfo callMth = invokeInsn.getCallMth(); ArgType declClsType = callMth.getDeclClass().getType(); ArgType callClsType; @@ -158,12 +165,7 @@ public class MethodInvokeVisitor extends AbstractVisitor { } else { callClsType = declClsType; } - - Map typeVarsMapping = root.getTypeUtils().getTypeVariablesMapping(callClsType); - resolveTypeVars(mthDetails, typeVarsMapping); - for (IMethodDetails m : overloadedMethods) { - resolveTypeVars(m, typeVarsMapping); - } + return root.getTypeUtils().getTypeVariablesMapping(callClsType); } private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List compilerVarTypes, List castTypes) { @@ -199,9 +201,11 @@ public class MethodInvokeVisitor extends AbstractVisitor { } } - private void resolveTypeVars(IMethodDetails mthDetails, Map typeVarsMapping) { + private IMethodDetails resolveTypeVars(IMethodDetails mthDetails, Map typeVarsMapping) { List argTypes = mthDetails.getArgTypes(); int argsCount = argTypes.size(); + boolean fixed = false; + List fixedArgTypes = new ArrayList<>(argsCount); for (int argNum = 0; argNum < argsCount; argNum++) { ArgType argType = argTypes.get(argNum); if (argType == null) { @@ -213,9 +217,28 @@ public class MethodInvokeVisitor extends AbstractVisitor { // type variables erased from method info by compiler resolvedType = mthDetails.getMethodInfo().getArgumentsTypes().get(argNum); } - argTypes.set(argNum, resolvedType); + fixedArgTypes.add(resolvedType); + fixed = true; + } else { + fixedArgTypes.add(argType); } } + ArgType returnType = mthDetails.getReturnType(); + if (returnType.containsTypeVariable()) { + ArgType resolvedType = root.getTypeUtils().replaceTypeVariablesUsingMap(returnType, typeVarsMapping); + if (resolvedType == null || resolvedType.containsTypeVariable()) { + returnType = mthDetails.getMethodInfo().getReturnType(); + fixed = true; + } + } + + if (!fixed) { + return mthDetails; + } + MutableMethodDetails mutableMethodDetails = new MutableMethodDetails(mthDetails); + mutableMethodDetails.setArgTypes(fixedArgTypes); + mutableMethodDetails.setRetType(returnType); + return mutableMethodDetails; } private List searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List overloadedMethods, diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java new file mode 100644 index 000000000..9b2be9855 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/methods/MutableMethodDetails.java @@ -0,0 +1,83 @@ +package jadx.core.dex.visitors.methods; + +import java.util.Collections; +import java.util.List; + +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.GenericTypeParameter; +import jadx.core.dex.nodes.IMethodDetails; + +public class MutableMethodDetails implements IMethodDetails { + + private final MethodInfo mthInfo; + private ArgType retType; + private List argTypes; + private List typeParams; + private List throwTypes; + private boolean varArg; + + public MutableMethodDetails(IMethodDetails base) { + this.mthInfo = base.getMethodInfo(); + this.retType = base.getReturnType(); + this.argTypes = Collections.unmodifiableList(base.getArgTypes()); + this.typeParams = Collections.unmodifiableList(base.getTypeParameters()); + this.throwTypes = Collections.unmodifiableList(base.getThrows()); + this.varArg = base.isVarArg(); + } + + @Override + public MethodInfo getMethodInfo() { + return mthInfo; + } + + @Override + public ArgType getReturnType() { + return retType; + } + + @Override + public List getArgTypes() { + return argTypes; + } + + @Override + public List getTypeParameters() { + return typeParams; + } + + @Override + public List getThrows() { + return throwTypes; + } + + @Override + public boolean isVarArg() { + return varArg; + } + + public void setRetType(ArgType retType) { + this.retType = retType; + } + + public void setArgTypes(List argTypes) { + this.argTypes = argTypes; + } + + public void setTypeParams(List typeParams) { + this.typeParams = typeParams; + } + + public void setThrowTypes(List throwTypes) { + this.throwTypes = throwTypes; + } + + public void setVarArg(boolean varArg) { + this.varArg = varArg; + } + + @Override + public String toString() { + return "Mutable" + toAttrString(); + } +} From 404136cd72cca845e7cccbc57ffdd6aec31b572e Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 10 May 2020 14:20:27 +0100 Subject: [PATCH 29/55] fix: improve type inference for generics in invoke insn (#927) --- .../typeinference/TypeBoundInvokeUse.java | 78 +++++++++++++++++ .../typeinference/TypeInferenceVisitor.java | 26 ++++-- .../visitors/typeinference/TypeUpdate.java | 83 ++++++++++++++----- .../integration/types/TestGenerics5.java | 44 ++++++++++ 4 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeUse.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeUse.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeUse.java new file mode 100644 index 000000000..7dfa14ab9 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundInvokeUse.java @@ -0,0 +1,78 @@ +package jadx.core.dex.visitors.typeinference; + +import jadx.core.dex.instructions.BaseInvokeNode; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.RootNode; + +/** + * Special dynamic bound for invoke with generics. + * Arguments bound type calculated using instance generic type. + */ +public final class TypeBoundInvokeUse implements ITypeBoundDynamic { + private final RootNode root; + private final BaseInvokeNode invokeNode; + private final RegisterArg arg; + private final ArgType genericArgType; + + public TypeBoundInvokeUse(RootNode root, BaseInvokeNode invokeNode, RegisterArg arg, ArgType genericArgType) { + this.root = root; + this.invokeNode = invokeNode; + this.arg = arg; + this.genericArgType = genericArgType; + } + + @Override + public BoundEnum getBound() { + return BoundEnum.USE; + } + + @Override + public ArgType getType(TypeUpdateInfo updateInfo) { + return getArgType(updateInfo.getType(invokeNode.getInstanceArg()), updateInfo.getType(arg)); + } + + @Override + public ArgType getType() { + return getArgType(invokeNode.getInstanceArg().getType(), arg.getType()); + } + + private ArgType getArgType(ArgType instanceType, ArgType argType) { + ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(instanceType, genericArgType); + if (resultGeneric != null) { + return resultGeneric; + } + return argType; + } + + @Override + public RegisterArg getArg() { + return arg; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TypeBoundInvokeUse that = (TypeBoundInvokeUse) o; + return invokeNode.equals(that.invokeNode); + } + + @Override + public int hashCode() { + return invokeNode.hashCode(); + } + + @Override + public String toString() { + return "InvokeAssign{" + invokeNode.getCallMth().getShortId() + + ", argType=" + genericArgType + + ", currentType=" + getType() + + ", instanceArg=" + invokeNode.getInstanceArg() + + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 87ec0b3ec..ac28c858e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -298,17 +298,31 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return null; } if (insn instanceof BaseInvokeNode) { - IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails((BaseInvokeNode) insn); - if (methodDetails != null) { - if (methodDetails.getArgTypes().stream().anyMatch(ArgType::containsTypeVariable)) { - // don't add const bound for generic type variables - return null; - } + TypeBoundInvokeUse invokeUseBound = makeInvokeUseBound(regArg, (BaseInvokeNode) insn); + if (invokeUseBound != null) { + return invokeUseBound; } } return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg); } + private TypeBoundInvokeUse makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) { + InsnArg instanceArg = invoke.getInstanceArg(); + if (instanceArg == null || instanceArg == regArg) { + return null; + } + IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invoke); + if (methodDetails == null) { + return null; + } + int argIndex = invoke.getArgIndex(regArg) - invoke.getFirstArgOffset(); + ArgType argType = methodDetails.getArgTypes().get(argIndex); + if (!argType.containsTypeVariable()) { + return null; + } + return new TypeBoundInvokeUse(root, invoke, regArg, argType); + } + private boolean tryPossibleTypes(SSAVar var, ArgType type) { List types = makePossibleTypesList(type); for (ArgType candidateType : types) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 35f6ccb51..47bf57141 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; +import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; @@ -19,8 +20,10 @@ import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -278,27 +281,58 @@ public final class TypeUpdate { } private TypeUpdateResult invokeListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { - if (insn.getResult() == null) { + InvokeNode invoke = (InvokeNode) insn; + if (isAssign(invoke, arg)) { + // TODO: implement backward type propagation (from result to instance) return SAME; } - if (candidateType.containsTypeVariable()) { - InvokeNode invokeNode = (InvokeNode) insn; - if (isAssign(insn, arg)) { - // TODO: implement backward type propagation (from result to instance) + if (invoke.getInstanceArg() == arg && candidateType.containsGeneric()) { + // resolve result and arg types from generic instance type + IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invoke); + if (methodDetails == null) { return SAME; - } else { - ArgType returnType = root.getMethodUtils().getMethodGenericReturnType(invokeNode); - if (returnType == null) { - return SAME; - } - ArgType resultGeneric = root.getTypeUtils().replaceClassGenerics(candidateType, returnType); - if (resultGeneric == null) { - return SAME; - } - return updateTypeChecked(updateInfo, insn.getResult(), resultGeneric); } + TypeUtils typeUtils = root.getTypeUtils(); + Map typeVarsMap = typeUtils.getTypeVariablesMapping(candidateType); + if (typeVarsMap.isEmpty()) { + return SAME; + } + + boolean allSame = true; + if (invoke.getResult() != null) { + ArgType returnType = typeUtils.replaceTypeVariablesUsingMap(methodDetails.getReturnType(), typeVarsMap); + if (returnType != null) { + TypeUpdateResult result = updateTypeChecked(updateInfo, invoke.getResult(), returnType); + if (result == REJECT) { + return REJECT; + } + if (result == CHANGED) { + allSame = false; + } + } + } + + int argOffset = invoke.getFirstArgOffset(); + List argTypes = methodDetails.getArgTypes(); + int argsCount = argTypes.size(); + for (int i = 0; i < argsCount; i++) { + ArgType genericArgType = argTypes.get(i); + ArgType resultArgType = typeUtils.replaceClassGenerics(candidateType, genericArgType); + if (resultArgType != null) { + InsnArg invokeArg = invoke.getArg(argOffset + i); + TypeUpdateResult result = updateTypeChecked(updateInfo, invokeArg, resultArgType); + if (result == REJECT) { + return REJECT; + } + if (result == CHANGED) { + allSame = false; + } + } + } + return allSame ? SAME : CHANGED; } return SAME; + } private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { @@ -377,12 +411,21 @@ public final class TypeUpdate { } private TypeUpdateResult checkCastListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { - if (!isAssign(insn, arg)) { - return SAME; + IndexInsnNode checkCast = (IndexInsnNode) insn; + if (isAssign(insn, arg)) { + InsnArg insnArg = insn.getArg(0); + TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); + return result == REJECT ? SAME : result; } - InsnArg insnArg = insn.getArg(0); - TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); - return result == REJECT ? SAME : result; + if (candidateType.containsGeneric()) { + ArgType castType = (ArgType) checkCast.getIndex(); + TypeCompareEnum compResult = comparator.compareTypes(candidateType, castType); + if (compResult == TypeCompareEnum.NARROW_BY_GENERIC) { + // propagate generic type to result + return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType); + } + } + return SAME; } private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { 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 new file mode 100644 index 000000000..2621cc19f --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics5.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.types; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestGenerics5 extends IntegrationTest { + + public static class TestCls { + private InheritableThreadLocal> inheritableThreadLocal; + + public void put(String key, String val) { + if (key == null) { + throw new IllegalArgumentException("key cannot be null"); + } + Map map = this.inheritableThreadLocal.get(); + if (map == null) { + map = new HashMap<>(); + this.inheritableThreadLocal.set(map); + } + map.put(key, val); + } + + public void remove(String key) { + Map map = this.inheritableThreadLocal.get(); + if (map != null) { + map.remove(key); + } + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .countString(2, "Map map = this.inheritableThreadLocal.get();"); + } +} From c05368d92e1758ce82c64c9688be3a7e63bff1bf Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 10 May 2020 15:45:18 +0100 Subject: [PATCH 30/55] style: prefer use Stream.of method --- jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java | 4 ++-- jadx-core/src/main/java/jadx/core/clsp/ClsSet.java | 6 +++--- .../src/main/java/jadx/core/codegen/ClassGen.java | 6 ++++-- .../jadx/core/dex/visitors/PrepareForCodeGen.java | 11 ++++------- .../src/main/java/jadx/core/utils/DebugUtils.java | 6 +++--- jadx-core/src/test/java/jadx/tests/api/SmaliTest.java | 4 ++-- .../java/jadx/gui/utils/search/StringRefTest.java | 4 ++-- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 31a2aea7c..50d6978d5 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -1,12 +1,12 @@ package jadx.cli; import java.util.ArrayList; -import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.beust.jcommander.IStringConverter; import com.beust.jcommander.Parameter; @@ -353,7 +353,7 @@ public class JadxCLIArgs { } public static String enumValuesString(Enum[] values) { - return Arrays.stream(values) + return Stream.of(values) .map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'') .collect(Collectors.joining(", ")); } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index 565a11b9b..e4c6636f6 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -13,12 +13,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -88,7 +88,7 @@ public class ClsSet { } if (LOG.isDebugEnabled()) { long time = System.currentTimeMillis() - startTime; - int methodsCount = Arrays.stream(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum(); + int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum(); LOG.debug("Load class set in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount); } } @@ -246,7 +246,7 @@ public class ClsSet { writeMethod(out, method, names); } } - int methodsCount = Arrays.stream(classes).mapToInt(c -> c.getMethodsMap().size()).sum(); + int methodsCount = Stream.of(classes).mapToInt(c -> c.getMethodsMap().size()).sum(); LOG.info("Classes: {}, methods: {}, file size: {}B", classes.length, methodsCount, out.size()); } 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 11d30f048..3c863462e 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -1,6 +1,7 @@ package jadx.core.codegen; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -8,11 +9,11 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import com.android.dx.rop.code.AccessFlags; -import com.google.common.collect.Streams; import jadx.api.ICodeInfo; import jadx.api.JadxArgs; @@ -252,7 +253,8 @@ public class ClassGen { } private void addInnerClsAndMethods(CodeWriter clsCode) { - Streams.concat(cls.getInnerClasses().stream(), cls.getMethods().stream()) + Stream.of(cls.getInnerClasses(), cls.getMethods()) + .flatMap(Collection::stream) .filter(node -> !node.contains(AFlag.DONT_GENERATE)) .sorted(Comparator.comparingInt(LineAttrNode::getSourceLine)) .forEach(node -> { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java index 5f3b33c95..c6dda47f6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -1,15 +1,15 @@ package jadx.core.dex.visitors; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; -import com.google.common.collect.Streams; - import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; @@ -266,11 +266,8 @@ public class PrepareForCodeGen extends AbstractVisitor { for (ClassNode innerClass : cls.getInnerClasses()) { setClassSourceLine(innerClass); } - - int minLine = Streams.concat( - cls.getMethods().stream(), - cls.getInnerClasses().stream(), - cls.getFields().stream()) + int minLine = Stream.of(cls.getMethods(), cls.getInnerClasses(), cls.getFields()) + .flatMap(Collection::stream) .filter(mth -> !mth.contains(AFlag.DONT_GENERATE)) .filter(mth -> mth.getSourceLine() != 0) .mapToInt(LineAttrNode::getSourceLine) diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java index 217e78a38..cb55c9f88 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -1,13 +1,13 @@ package jadx.core.utils; import java.io.File; -import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jetbrains.annotations.TestOnly; import org.slf4j.Logger; @@ -128,7 +128,7 @@ public class DebugUtils { ig.makeInsn(insn, code); String codeStr = code.finish().getCodeStr(); - List insnStrings = Arrays.stream(codeStr.split(NL)) + List insnStrings = Stream.of(codeStr.split(NL)) .filter(StringUtils::notBlank) .map(s -> "|> " + s) .collect(Collectors.toList()); @@ -150,7 +150,7 @@ public class DebugUtils { private static void printWithAttributes(CodeWriter cw, String indent, String codeStr, IAttributeNode attrNode) { String str = attrNode.isAttrStorageEmpty() ? codeStr : codeStr + ' ' + attrNode.getAttributesString(); - List attrStrings = Arrays.stream(str.split(NL)) + List attrStrings = Stream.of(str.split(NL)) .filter(StringUtils::notBlank) .collect(Collectors.toList()); Iterator it = attrStrings.iterator(); 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 241760dcb..e41160696 100644 --- a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java @@ -1,10 +1,10 @@ package jadx.tests.api; import java.io.File; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import org.jf.smali.Smali; @@ -86,7 +86,7 @@ public abstract class SmaliTest extends IntegrationTest { File smaliDir = new File(SMALI_TESTS_DIR, smaliFilesDir); String[] smaliFileNames = smaliDir.list((dir, name) -> name.endsWith(".smali")); assertThat("Smali files not found in " + smaliDir, smaliFileNames, notNullValue()); - return Arrays.stream(smaliFileNames) + return Stream.of(smaliFileNames) .map(file -> new File(smaliDir, file)) .collect(Collectors.toList()); } diff --git a/jadx-gui/src/test/java/jadx/gui/utils/search/StringRefTest.java b/jadx-gui/src/test/java/jadx/gui/utils/search/StringRefTest.java index 28f991e3c..b6dd624d1 100644 --- a/jadx-gui/src/test/java/jadx/gui/utils/search/StringRefTest.java +++ b/jadx-gui/src/test/java/jadx/gui/utils/search/StringRefTest.java @@ -1,8 +1,8 @@ package jadx.gui.utils.search; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -62,7 +62,7 @@ class StringRefTest { } private static void checkSplit(String str, String splitBy, String... result) { - List expectedStringRegList = Arrays.stream(result).map(StringRef::fromStr).collect(Collectors.toList()); + List expectedStringRegList = Stream.of(result).map(StringRef::fromStr).collect(Collectors.toList()); assertThat(StringRef.split(str, splitBy), is(expectedStringRegList)); // compare with original split From 3968222744b1fb40d1f57006d59c58c562218669 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 10 May 2020 15:46:24 +0100 Subject: [PATCH 31/55] style: fix incorrect import --- .../java/jadx/core/dex/attributes/nodes/PhiListAttr.java | 5 ++--- .../main/java/jadx/core/dex/visitors/DotGraphVisitor.java | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) 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 d4bf9f34a..41c61d7cf 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 @@ -3,12 +3,11 @@ package jadx.core.dex.attributes.nodes; import java.util.LinkedList; import java.util.List; +import jadx.core.codegen.CodeWriter; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; import jadx.core.dex.instructions.PhiInsn; -import static com.google.common.base.Ascii.NL; - public class PhiListAttr implements IAttribute { private final List list = new LinkedList<>(); @@ -30,7 +29,7 @@ public class PhiListAttr implements IAttribute { sb.append('r').append(phiInsn.getResult().getRegNum()).append(' '); } for (PhiInsn phiInsn : list) { - sb.append(NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString()); + sb.append(CodeWriter.NL).append(" ").append(phiInsn).append(' ').append(phiInsn.getAttributesString()); } return sb.toString(); } 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 29b084930..5bf2900a9 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 @@ -297,6 +297,7 @@ public class DotGraphVisitor extends AbstractVisitor { .replace("\"", "\\\"") .replace("-", "\\-") .replace("|", "\\|") + .replace(CodeWriter.NL, NL) .replace("\n", NL); } } From 0692464b85f29c762064612e2ea5208238d23a33 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 11 May 2020 20:42:06 +0100 Subject: [PATCH 32/55] fix: mark override methods and fix return type (#913) --- jadx-core/src/main/java/jadx/core/Jadx.java | 2 + .../main/java/jadx/core/clsp/ClspGraph.java | 50 +++--- .../main/java/jadx/core/codegen/ClassGen.java | 2 +- .../java/jadx/core/codegen/MethodGen.java | 20 +++ .../java/jadx/core/dex/attributes/AType.java | 2 + .../attributes/nodes/MethodOverrideAttr.java | 30 ++++ .../core/dex/instructions/args/ArgType.java | 8 +- .../java/jadx/core/dex/nodes/MethodNode.java | 5 + .../jadx/core/dex/nodes/utils/TypeUtils.java | 13 +- .../dex/visitors/OverrideMethodVisitor.java | 148 ++++++++++++++++++ .../typeinference/TypeInferenceVisitor.java | 2 +- .../visitors/typeinference/TypeSearch.java | 2 +- .../java/jadx/tests/api/IntegrationTest.java | 8 + .../tests/integration/enums/TestEnums2.java | 6 +- .../integration/enums/TestEnumsInterface.java | 4 +- .../generics/TestGenericsMthOverride.java | 5 + .../generics/TestMethodOverride.java | 20 +++ .../smali/generics/TestMethodOverride.smali | 38 +++++ 18 files changed, 328 insertions(+), 37 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java create mode 100644 jadx-core/src/test/smali/generics/TestMethodOverride.smali diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index cf09e7dcd..674209e8e 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -57,6 +57,8 @@ public class Jadx { passes.add(new BlockExceptionHandler()); passes.add(new BlockFinish()); + passes.add(new OverrideMethodVisitor()); + passes.add(new SSATransform()); passes.add(new MoveInlineVisitor()); passes.add(new ConstructorVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java index c8a7e9eaa..f58c07204 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +30,7 @@ public class ClspGraph { private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class); private final RootNode root; - private final Map> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>()); + private final Map> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>()); private Map nameMap; private final Set missingClasses = new HashSet<>(); @@ -116,7 +117,7 @@ public class ClspGraph { * @return {@code clsName} instanceof {@code implClsName} */ public boolean isImplements(String clsName, String implClsName) { - Set anc = getAncestors(clsName); + Set anc = getSuperTypes(clsName); return anc.contains(implClsName); } @@ -142,7 +143,7 @@ public class ClspGraph { if (isImplements(clsName, implClsName)) { return implClsName; } - Set anc = getAncestors(clsName); + Set anc = getSuperTypes(clsName); return searchCommonParent(anc, cls); } @@ -163,35 +164,42 @@ public class ClspGraph { return null; } - public Set getAncestors(String clsName) { - Set result = ancestorCache.get(clsName); - if (result != null) { - return result; + public Set getSuperTypes(String clsName) { + Set fromCache = superTypesCache.get(clsName); + if (fromCache != null) { + return fromCache; } ClspClass cls = nameMap.get(clsName); if (cls == null) { missingClasses.add(clsName); return Collections.emptySet(); } - result = new HashSet<>(); - addAncestorsNames(cls, result); + Set result = new HashSet<>(); + addSuperTypes(cls, result); + return putInSuperTypesCache(clsName, result); + } + + @NotNull + private Set putInSuperTypesCache(String clsName, Set result) { if (result.isEmpty()) { - result = Collections.emptySet(); + Set empty = Collections.emptySet(); + superTypesCache.put(clsName, result); + return empty; } - ancestorCache.put(clsName, result); + superTypesCache.put(clsName, result); return result; } - private void addAncestorsNames(ClspClass cls, Set result) { - boolean isNew = result.add(cls.getName()); - if (isNew) { - for (ArgType parentType : cls.getParents()) { - if (parentType == null) { - continue; - } - ClspClass parentCls = getClspClass(parentType); - if (parentCls != null) { - addAncestorsNames(parentCls, result); + private void addSuperTypes(ClspClass cls, Set result) { + for (ArgType parentType : cls.getParents()) { + if (parentType == null) { + continue; + } + ClspClass parentCls = getClspClass(parentType); + if (parentCls != null) { + boolean isNew = result.add(parentCls.getName()); + if (isNew) { + addSuperTypes(parentCls, result); } } } 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 3c863462e..1e1bcb100 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -317,7 +317,7 @@ public class ClassGen { public void addMethodCode(CodeWriter code, MethodNode mth) throws CodegenException { CodeGenUtils.addComments(code, mth); - if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) { + if (mth.isNoCode()) { MethodGen mthGen = new MethodGen(this, mth); mthGen.addDefinition(code); code.add(';'); 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 09d4ed00f..04606b059 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -14,6 +14,7 @@ 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.JumpInfo; +import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; @@ -21,6 +22,7 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.CatchAttr; @@ -72,6 +74,7 @@ public class MethodGen { code.attachDefinition(mth); return false; } + addOverrideAnnotation(code, mth); annotationGen.addForMethod(code, mth); AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags(); @@ -146,6 +149,23 @@ public class MethodGen { return true; } + private void addOverrideAnnotation(CodeWriter code, MethodNode mth) { + MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE); + if (overrideAttr == null) { + return; + } + code.startLine("@Override"); + code.add(" // "); + Iterator it = overrideAttr.getOverrideList().iterator(); + while (it.hasNext()) { + IMethodDetails methodDetails = it.next(); + code.add(methodDetails.getMethodInfo().getDeclClass().getAliasFullName()); + if (it.hasNext()) { + code.add(", "); + } + } + } + private void addMethodArguments(CodeWriter code, List args) { MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS); int i = 0; 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 6003ae9c8..a6e96428d 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 @@ -20,6 +20,7 @@ import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr; +import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr; @@ -63,6 +64,7 @@ public class 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<>(); // region public static final AType DECLARE_VARIABLES = new AType<>(); 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 new file mode 100644 index 000000000..fc6d3aa71 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodOverrideAttr.java @@ -0,0 +1,30 @@ +package jadx.core.dex.attributes.nodes; + +import java.util.List; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.nodes.IMethodDetails; + +public class MethodOverrideAttr implements IAttribute { + + private final List overrideList; + + public MethodOverrideAttr(List overrideList) { + this.overrideList = overrideList; + } + + public List getOverrideList() { + return overrideList; + } + + @Override + public AType getType() { + return AType.METHOD_OVERRIDE; + } + + @Override + public String toString() { + return "METHOD_OVERRIDE: " + overrideList; + } +} 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 fe6fff83d..cdeb21075 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 @@ -10,6 +10,7 @@ import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.typeinference.TypeCompareEnum; import jadx.core.utils.Utils; public abstract class ArgType { @@ -578,11 +579,8 @@ public abstract class ArgType { if (from.equals(to)) { return false; } - if (from.isObject() && to.isObject() - && root.getClsp().isImplements(from.getObject(), to.getObject())) { - return false; - } - return true; + TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to); + return !result.isNarrow(); } public static boolean isInstanceOf(RootNode root, ArgType type, ArgType of) { 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 ab595be06..edca171ec 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 @@ -180,6 +180,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, if (types == null) { this.retType = mthInfo.getReturnType(); this.argTypes = mthInfo.getArgumentsTypes(); + this.typeParameters = Collections.emptyList(); } else { this.argTypes = Collections.unmodifiableList(types); } @@ -283,6 +284,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return retType; } + public void updateReturnType(ArgType type) { + this.retType = type; + } + public boolean isVoidReturn() { return mthInfo.getReturnType().equals(ArgType.VOID); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index 76c83ad1d..e7e1f4449 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -50,13 +50,14 @@ public class TypeUtils { */ @Nullable public ArgType replaceClassGenerics(ArgType instanceType, ArgType typeWithGeneric) { - if (typeWithGeneric != null) { - Map replaceMap = getTypeVariablesMapping(instanceType); - if (!replaceMap.isEmpty()) { - return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap); - } + if (typeWithGeneric == null) { + return null; } - return null; + Map replaceMap = getTypeVariablesMapping(instanceType); + if (replaceMap.isEmpty()) { + return null; + } + return replaceTypeVariablesUsingMap(typeWithGeneric, replaceMap); } public Map getTypeVariablesMapping(ArgType clsType) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java new file mode 100644 index 000000000..00b6743a5 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -0,0 +1,148 @@ +package jadx.core.dex.visitors; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import jadx.core.clsp.ClspClass; +import jadx.core.clsp.ClspMethod; +import jadx.core.dex.attributes.nodes.MethodOverrideAttr; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.IMethodDetails; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.typeinference.TypeCompare; +import jadx.core.dex.visitors.typeinference.TypeCompareEnum; +import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; +import jadx.core.utils.exceptions.JadxException; + +@JadxVisitor( + name = "OverrideMethodVisitor", + desc = "Mark override methods and revert type erasure", + runBefore = { + TypeInferenceVisitor.class + } +) +public class OverrideMethodVisitor extends AbstractVisitor { + + @Override + public boolean visit(ClassNode cls) throws JadxException { + RootNode root = cls.root(); + List superTypes = collectSuperTypes(cls); + for (MethodNode mth : cls.getMethods()) { + if (mth.isConstructor()) { + continue; + } + String signature = mth.getMethodInfo().makeSignature(false); + List overrideList = collectOverrideMethods(root, superTypes, signature); + if (!overrideList.isEmpty()) { + mth.addAttr(new MethodOverrideAttr(overrideList)); + fixMethodReturnType(mth, overrideList, superTypes); + } + } + return true; + } + + private List collectOverrideMethods(RootNode root, List superTypes, String signature) { + List overrideList = new ArrayList<>(); + for (ArgType superType : superTypes) { + ClassNode classNode = root.resolveClass(superType); + if (classNode != null) { + for (MethodNode mth : classNode.getMethods()) { + String mthShortId = mth.getMethodInfo().getShortId(); + if (mthShortId.startsWith(signature)) { + overrideList.add(mth); + } + } + } else { + ClspClass clsDetails = root.getClsp().getClsDetails(superType); + if (clsDetails != null) { + Map methodsMap = clsDetails.getMethodsMap(); + for (Map.Entry entry : methodsMap.entrySet()) { + String mthShortId = entry.getKey(); + if (mthShortId.startsWith(signature)) { + overrideList.add(entry.getValue()); + } + } + } + } + } + return overrideList; + } + + private List collectSuperTypes(ClassNode cls) { + Map superTypes = new HashMap<>(); + collectSuperTypes(cls, superTypes); + return new ArrayList<>(superTypes.values()); + } + + private void collectSuperTypes(ClassNode cls, Map superTypes) { + RootNode root = cls.root(); + ArgType superClass = cls.getSuperClass(); + if (superClass != null && !Objects.equals(superClass, ArgType.OBJECT)) { + addSuperType(root, superTypes, superClass); + } + for (ArgType iface : cls.getInterfaces()) { + addSuperType(root, superTypes, iface); + } + } + + private void addSuperType(RootNode root, Map superTypesMap, ArgType superType) { + superTypesMap.put(superType.getObject(), superType); + ClassNode classNode = root.resolveClass(superType); + if (classNode == null) { + for (String superCls : root.getClsp().getSuperTypes(superType.getObject())) { + ArgType type = ArgType.object(superCls); + superTypesMap.put(type.getObject(), type); + } + } else { + collectSuperTypes(classNode, superTypesMap); + } + } + + private void fixMethodReturnType(MethodNode mth, List overrideList, List superTypes) { + ArgType returnType = mth.getReturnType(); + int updateCount = 0; + for (IMethodDetails baseMth : overrideList) { + if (updateReturnType(mth, baseMth, superTypes)) { + updateCount++; + } + } + if (updateCount == 0) { + return; + } + if (updateCount == 1) { + mth.addComment("Return type fixed from '" + returnType + "' to match base method"); + } else { + mth.addWarnComment("Due to multiple override return type can be incorrect, original value: " + returnType); + } + } + + private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, List superTypes) { + ArgType baseReturnType = baseMth.getReturnType(); + if (mth.getReturnType().equals(baseReturnType)) { + return false; + } + if (!baseReturnType.containsTypeVariable()) { + return false; + } + TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); + ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); + for (ArgType superType : superTypes) { + TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); + if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { + ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType); + if (targetRetType != null + && !targetRetType.containsTypeVariable() + && !targetRetType.equals(mth.getReturnType())) { + mth.updateReturnType(targetRetType); + return true; + } + } + } + return false; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index ac28c858e..729642eaf 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -521,7 +521,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { } ClspGraph clsp = mth.root().getClsp(); for (ArgType objType : objTypes) { - for (String ancestor : clsp.getAncestors(objType.getObject())) { + for (String ancestor : clsp.getSuperTypes(objType.getObject())) { ArgType ancestorType = ArgType.object(ancestor); TypeUpdateResult result = typeUpdate.applyWithWiderAllow(var, ancestorType); if (result == TypeUpdateResult.CHANGED) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java index bd8e7873d..3a4e139a6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java @@ -281,7 +281,7 @@ public class TypeSearch { private List getWiderTypes(ArgType type) { if (type.isTypeKnown()) { if (type.isObject()) { - Set ancestors = mth.root().getClsp().getAncestors(type.getObject()); + Set ancestors = mth.root().getClsp().getSuperTypes(type.getObject()); return ancestors.stream().map(ArgType::object).collect(Collectors.toList()); } } else { 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 004c8d581..2e4bec5fb 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.jar.JarOutputStream; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -458,6 +459,13 @@ public abstract class IntegrationTest extends TestUtils { return files; } + @NotNull + protected static String removeLineComments(ClassNode cls) { + String code = cls.getCode().getCodeStr().replaceAll("\\W*//.*", ""); + System.out.println(code); + return code; + } + public JadxArgs getArgs() { return args; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java index 0cb362bf5..65c9fc3b9 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums2.java @@ -14,11 +14,13 @@ public class TestEnums2 extends IntegrationTest { public enum Operation { PLUS { + @Override public int apply(int x, int y) { return x + y; } }, MINUS { + @Override public int apply(int x, int y) { return x - y; } @@ -31,16 +33,18 @@ public class TestEnums2 extends IntegrationTest { @Test public void test() { ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); + String code = removeLineComments(cls); assertThat(code, JadxMatchers.containsLines(1, "public enum Operation {", indent(1) + "PLUS {", + indent(2) + "@Override", indent(2) + "public int apply(int x, int y) {", indent(3) + "return x + y;", indent(2) + '}', indent(1) + "},", indent(1) + "MINUS {", + indent(2) + "@Override", indent(2) + "public int apply(int x, int y) {", indent(3) + "return x - y;", indent(2) + '}', diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java index 4b782a040..a28ed65b9 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java +++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnumsInterface.java @@ -35,16 +35,18 @@ public class TestEnumsInterface extends IntegrationTest { @Test public void test() { ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); + String code = removeLineComments(cls); assertThat(code, JadxMatchers.containsLines(1, "public enum Operation implements IOperation {", indent(1) + "PLUS {", + indent(2) + "@Override", indent(2) + "public int apply(int x, int y) {", indent(3) + "return x + y;", indent(2) + '}', indent(1) + "},", indent(1) + "MINUS {", + indent(2) + "@Override", indent(2) + "public int apply(int x, int y) {", indent(3) + "return x - y;", indent(2) + '}', diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java index bfe83ed73..75d85d531 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestGenericsMthOverride.java @@ -6,6 +6,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static jadx.tests.api.utils.JadxMatchers.countString; import static org.hamcrest.MatcherAssert.assertThat; public class TestGenericsMthOverride extends IntegrationTest { @@ -54,5 +55,9 @@ public class TestGenericsMthOverride extends IntegrationTest { assertThat(code, containsOne("public Y method(Object x) {")); assertThat(code, containsOne("public Y method(Exception x) {")); assertThat(code, containsOne("public Object method(Object x) {")); + + assertThat(code, countString(3, "@Override")); + // TODO: @Override missing for class C + // assertThat(code, countString(4, "@Override")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java new file mode 100644 index 000000000..0d7887fe7 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java @@ -0,0 +1,20 @@ +package jadx.tests.integration.generics; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestMethodOverride extends SmaliTest { + + @Test + public void test() { + disableCompilation(); + + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("String createFromParcel(Parcel parcel) {") + .containsOne("@Override"); + } +} diff --git a/jadx-core/src/test/smali/generics/TestMethodOverride.smali b/jadx-core/src/test/smali/generics/TestMethodOverride.smali new file mode 100644 index 000000000..942bf87d1 --- /dev/null +++ b/jadx-core/src/test/smali/generics/TestMethodOverride.smali @@ -0,0 +1,38 @@ +.class public final Lgenerics/TestMethodOverride; +.super Ljava/lang/Object; + +# interfaces +.implements Landroid/os/Parcelable$Creator; + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/lang/Object;", + "Landroid/os/Parcelable$Creator<", + "Ljava/lang/String;", + ">;" + } +.end annotation + + +# direct methods +.method public constructor ()V + .registers 1 + + .line 1 + invoke-direct {p0}, Ljava/lang/Object;->()V + + return-void +.end method + + +# virtual methods +.method public final synthetic createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object; + .registers 2 + + const/4 v0, 0x0 + + return-object v0 +.end method + From 85760cc844be7b7092cf8be37be8b9759ab31d8d Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 May 2020 17:17:22 +0100 Subject: [PATCH 33/55] fix: replace type variables in arrays (#913) --- .../core/dex/instructions/args/ArgType.java | 4 +- .../jadx/core/dex/nodes/utils/TypeUtils.java | 7 +++ .../core/dex/nodes/utils/TypeUtilsTest.java | 45 +++++++++++++++++++ .../generics/TestMethodOverride.java | 3 +- .../smali/generics/TestMethodOverride.smali | 9 ++++ 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java 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 cdeb21075..2ffec4f7a 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 @@ -5,6 +5,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import org.jetbrains.annotations.NotNull; + import jadx.core.Consts; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; @@ -114,7 +116,7 @@ public abstract class ArgType { return new OuterGenericObject((GenericObject) genericOuterType, (ObjectType) innerType); } - public static ArgType array(ArgType vtype) { + public static ArgType array(@NotNull ArgType vtype) { return new ArrayArg(vtype); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index e7e1f4449..2291ee2c0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -117,6 +117,13 @@ public class TypeUtils { if (replaceType.isGenericType()) { return replaceMap.get(replaceType); } + if (replaceType.isArray()) { + ArgType replaced = replaceTypeVariablesUsingMap(replaceType.getArrayElement(), replaceMap); + if (replaced == null) { + return null; + } + return ArgType.array(replaced); + } ArgType wildcardType = replaceType.getWildcardType(); if (wildcardType != null && wildcardType.containsTypeVariable()) { diff --git a/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java b/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java new file mode 100644 index 000000000..003975c48 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/dex/nodes/utils/TypeUtilsTest.java @@ -0,0 +1,45 @@ +package jadx.core.dex.nodes.utils; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jadx.api.JadxArgs; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.RootNode; + +import static jadx.core.dex.instructions.args.ArgType.STRING; +import static jadx.core.dex.instructions.args.ArgType.array; +import static jadx.core.dex.instructions.args.ArgType.generic; +import static jadx.core.dex.instructions.args.ArgType.genericType; +import static jadx.core.dex.instructions.args.ArgType.object; +import static org.assertj.core.api.Assertions.assertThat; + +class TypeUtilsTest { + private TypeUtils typeUtils; + + @BeforeEach + public void init() { + typeUtils = new TypeUtils(new RootNode(new JadxArgs())); + } + + @Test + void replaceTypeVariablesUsingMap() { + ArgType typeVar = genericType("T"); + ArgType listCls = object("java.util.List"); + Map typeMap = Collections.singletonMap(typeVar, STRING); + + replaceTypeVar(typeVar, typeMap, STRING); + replaceTypeVar(generic(listCls, typeVar), typeMap, generic(listCls, STRING)); + replaceTypeVar(array(typeVar), typeMap, array(STRING)); + } + + private void replaceTypeVar(ArgType typeVar, Map typeMap, ArgType expected) { + ArgType resultType = typeUtils.replaceTypeVariablesUsingMap(typeVar, typeMap); + assertThat(resultType) + .as("Replace %s using map %s", typeVar, typeMap) + .isEqualTo(expected); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java index 0d7887fe7..58d172087 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMethodOverride.java @@ -15,6 +15,7 @@ public class TestMethodOverride extends SmaliTest { assertThat(getClassNodeFromSmali()) .code() .containsOne("String createFromParcel(Parcel parcel) {") - .containsOne("@Override"); + .containsOne("String[] newArray(int i) {") + .countString(2, "@Override"); } } diff --git a/jadx-core/src/test/smali/generics/TestMethodOverride.smali b/jadx-core/src/test/smali/generics/TestMethodOverride.smali index 942bf87d1..1c5821571 100644 --- a/jadx-core/src/test/smali/generics/TestMethodOverride.smali +++ b/jadx-core/src/test/smali/generics/TestMethodOverride.smali @@ -36,3 +36,12 @@ return-object v0 .end method +.method public final synthetic newArray(I)[Ljava/lang/Object; + .registers 2 + + .line 4 + new-array p1, p1, [Ljava/lang/String; + + return-object p1 +.end method + From d59c99ddfed8c5f3104c3da112adb2948da9f247 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 May 2020 19:06:39 +0100 Subject: [PATCH 34/55] fix: attach method details before OverrideMethodVisitor --- jadx-core/src/main/java/jadx/core/Jadx.java | 2 +- .../java/jadx/core/dex/visitors/AttachMethodDetails.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 674209e8e..84dd65be2 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -57,6 +57,7 @@ public class Jadx { passes.add(new BlockExceptionHandler()); passes.add(new BlockFinish()); + passes.add(new AttachMethodDetails()); passes.add(new OverrideMethodVisitor()); passes.add(new SSATransform()); @@ -65,7 +66,6 @@ public class Jadx { passes.add(new InitCodeVariables()); passes.add(new MarkFinallyVisitor()); passes.add(new ConstInlineVisitor()); - passes.add(new AttachMethodDetails()); passes.add(new TypeInferenceVisitor()); if (args.isDebugInfo()) { passes.add(new DebugInfoApplyVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java index 24e2ec09a..9acc384a3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java @@ -8,13 +8,17 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.utils.MethodUtils; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; +import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.utils.exceptions.JadxException; @JadxVisitor( name = "Attach Method Details", desc = "Attach method details for invoke instructions", runBefore = { - CodeShrinkVisitor.class + CodeShrinkVisitor.class, + TypeInferenceVisitor.class, + MethodInvokeVisitor.class, + OverrideMethodVisitor.class } ) public class AttachMethodDetails extends AbstractVisitor { From 7cd77ae379065bed253e11e7bce24c5d9252b7e8 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 May 2020 19:23:49 +0100 Subject: [PATCH 35/55] fix: try raw types to help type inference (#913) --- .../core/dex/instructions/args/SSAVar.java | 1 - .../typeinference/TypeInferenceVisitor.java | 60 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java index c005f9e0c..6d8a10d7d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java @@ -270,7 +270,6 @@ public class SSAVar { if (!types.isEmpty()) { sb.append(", types: ").append(types); } - sb.append(", assign insn: ").append(getAssign().getParentInsn()); return sb.toString(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 729642eaf..0d51c31da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -92,7 +92,13 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (tryInsertAdditionalMove(mth)) { return true; } - return runMultiVariableSearch(mth); + if (runMultiVariableSearch(mth)) { + return true; + } + if (tryRemoveGenerics(mth)) { + return true; + } + return false; } /** @@ -124,11 +130,15 @@ public final class TypeInferenceVisitor extends AbstractVisitor { private boolean runMultiVariableSearch(MethodNode mth) { TypeSearch typeSearch = new TypeSearch(mth); try { - boolean success = typeSearch.run(); - if (!success) { + if (!typeSearch.run()) { mth.addWarn("Multi-variable type inference failed"); } - return success; + for (SSAVar var : mth.getSVars()) { + if (!var.getTypeInfo().getType().isTypeKnown()) { + return false; + } + } + return true; } catch (Exception e) { mth.addWarn("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); return false; @@ -366,6 +376,48 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return false; } + private boolean tryRemoveGenerics(MethodNode mth) { + boolean resolved = true; + for (SSAVar var : mth.getSVars()) { + ArgType type = var.getTypeInfo().getType(); + if (!type.isTypeKnown() && !var.isTypeImmutable() + && !tryRawType(mth, var)) { + resolved = false; + } + } + return resolved; + } + + private boolean tryRawType(MethodNode mth, SSAVar var) { + Set objTypes = new LinkedHashSet<>(); + for (ITypeBound bound : var.getTypeInfo().getBounds()) { + ArgType boundType = bound.getType(); + if (boundType.isTypeKnown() && boundType.isObject()) { + objTypes.add(boundType); + } + } + if (objTypes.isEmpty()) { + return false; + } + for (ArgType objType : objTypes) { + if (checkRawType(mth, var, objType)) { + mth.addDebugComment("Type inference failed for " + var.toShortString() + "." + + " Raw type applied. Possible types: " + Utils.listToString(objTypes)); + return true; + } + } + return false; + } + + private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) { + if (objType.isObject() && objType.containsGeneric()) { + ArgType rawType = ArgType.object(objType.getObject()); + TypeUpdateResult result = typeUpdate.applyWithWiderAllow(var, rawType); + return result == TypeUpdateResult.CHANGED; + } + return false; + } + private boolean trySplitConstInsns(MethodNode mth) { boolean constSplitted = false; for (SSAVar var : new ArrayList<>(mth.getSVars())) { From c7a12ad75b93be2c27a6befa6d7e27c98b72bd19 Mon Sep 17 00:00:00 2001 From: Jan S Date: Wed, 13 May 2020 19:25:45 +0200 Subject: [PATCH 36/55] fix(res): resource XML generation for parent attribute added (PR #933, #931) --- .../src/main/java/jadx/core/xmlgen/ResXmlGen.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java index 2ef3c6360..7794ef096 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java @@ -17,6 +17,7 @@ import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ValuesParser; import static jadx.core.xmlgen.ParserConstants.PLURALS_MAP; +import static jadx.core.xmlgen.ParserConstants.TYPE_REFERENCE; public class ResXmlGen { @@ -85,10 +86,16 @@ public class ResXmlGen { if (formatValue != null) { cw.add("\" format=\"").add(formatValue); } - cw.add("\">"); + cw.add("\""); } else { - cw.add("name=\"").add(ri.getKeyName()).add("\">"); + cw.add("name=\"").add(ri.getKeyName()).add('\"'); } + if (ri.getParentRef() != 0) { + String parent = vp.decodeValue(TYPE_REFERENCE, ri.getParentRef()); + cw.add(" parent=\"").add(parent).add('\"'); + } + cw.add(">"); + cw.incIndent(); for (RawNamedValue value : ri.getNamedValues()) { addItem(cw, itemTag, ri.getTypeName(), value); From 21e4dee0e2323b76ce25ac63fd8511bdf0a9ab41 Mon Sep 17 00:00:00 2001 From: Jan S Date: Wed, 13 May 2020 19:46:58 +0200 Subject: [PATCH 37/55] chore: exclude project build directories from code style checking (PR #934) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a6e21c2a4..9b32030fe 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ spotless { } format 'misc', { target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties' - targetExclude ".gradle/**", ".idea/**" + targetExclude ".gradle/**", ".idea/**", "*/build/**" lineEndings(com.diffplug.spotless.LineEnding.UNIX) encoding("UTF-8") From f482b8b95c121bedc70df1a3f30557be7e0c1f37 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 May 2020 21:52:00 +0100 Subject: [PATCH 38/55] fix: restart comment to escape strings in insn fallback dump --- .../java/jadx/core/codegen/MethodGen.java | 47 ++++++++++++++++--- .../core/dex/visitors/DotGraphVisitor.java | 4 +- .../dex/visitors/regions/CheckRegions.java | 2 +- 3 files changed, 44 insertions(+), 9 deletions(-) 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 04606b059..5840b40c5 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -16,6 +16,7 @@ import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.instructions.ConstStringNode; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.ArgType; @@ -35,6 +36,10 @@ 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; +import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP; +import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE; + public class MethodGen { private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class); @@ -221,7 +226,7 @@ public class MethodGen { public void addInstructions(CodeWriter code) throws CodegenException { if (mth.root().getArgs().isFallbackMode()) { - addFallbackMethodCode(code); + addFallbackMethodCode(code, FALLBACK_MODE); } else if (classGen.isFallbackMode()) { dumpInstructions(code); } else { @@ -249,7 +254,7 @@ public class MethodGen { public void dumpInstructions(CodeWriter code) { code.startLine("/*"); - addFallbackMethodCode(code); + addFallbackMethodCode(code, COMMENTED_DUMP); code.startLine("*/"); code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ") @@ -263,7 +268,7 @@ public class MethodGen { .add("\");"); } - public void addFallbackMethodCode(CodeWriter code) { + public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) { if (mth.getInstructions() == null) { // load original instructions try { @@ -285,11 +290,17 @@ public class MethodGen { if (mth.getThisArg() != null) { code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;"); } - addFallbackInsns(code, mth, insnArr, true); + addFallbackInsns(code, mth, insnArr, fallbackOption); code.decIndent(); } - public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) { + public enum FallbackOption { + FALLBACK_MODE, + BLOCK_DUMP, + COMMENTED_DUMP + } + + public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) { InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true); boolean attachInsns = mth.root().getArgs().isJsonOutput(); InsnNode prevInsn = null; @@ -297,7 +308,7 @@ public class MethodGen { if (insn == null) { continue; } - if (addLabels && needLabel(insn, prevInsn)) { + if (option != BLOCK_DUMP && needLabel(insn, prevInsn)) { code.decIndent(); code.startLine(getLabelName(insn.getOffset()) + ':'); code.incIndent(); @@ -306,7 +317,14 @@ public class MethodGen { continue; } try { - code.startLine(); + boolean escapeComment = isCommentEscapeNeeded(insn, option); + if (escapeComment) { + code.decIndent(); + code.startLine("*/"); + code.startLine("// "); + } else { + code.startLine(); + } if (attachInsns) { code.attachLineAnnotation(insn); } @@ -318,6 +336,11 @@ public class MethodGen { } } insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE); + if (escapeComment) { + code.startLine("/*"); + code.incIndent(); + } + CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); if (catchAttr != null) { code.add(" // " + catchAttr); @@ -330,6 +353,16 @@ public class MethodGen { } } + private static boolean isCommentEscapeNeeded(InsnNode insn, FallbackOption option) { + if (option == COMMENTED_DUMP) { + if (insn.getType() == InsnType.CONST_STR) { + String str = ((ConstStringNode) insn).getString(); + return str.contains("*/"); + } + } + return false; + } + private static boolean needLabel(InsnNode insn, InsnNode prevInsn) { if (insn.contains(AType.EXC_HANDLER)) { return true; 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 5bf2900a9..ed69bc25f 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 @@ -23,6 +23,8 @@ import jadx.core.utils.RegionUtils; import jadx.core.utils.StringUtils; import jadx.core.utils.Utils; +import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP; + public class DotGraphVisitor extends AbstractVisitor { private static final String NL = "\\l"; @@ -272,7 +274,7 @@ public class DotGraphVisitor extends AbstractVisitor { } else { CodeWriter code = new CodeWriter(); List instructions = block.getInstructions(); - MethodGen.addFallbackInsns(code, mth, instructions.toArray(new InsnNode[0]), false); + MethodGen.addFallbackInsns(code, mth, instructions.toArray(new InsnNode[0]), BLOCK_DUMP); String str = escape(code.newLine().toString()); if (str.startsWith(NL)) { str = str.substring(NL.length()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java index 45fc2d223..39efecbd7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java @@ -61,7 +61,7 @@ public class CheckRegions extends AbstractVisitor { && !block.contains(AFlag.ADDED_TO_REGION) && !block.contains(AFlag.DONT_GENERATE) && !block.contains(AFlag.REMOVE)) { - String blockCode = getBlockInsnStr(mth, block); + String blockCode = getBlockInsnStr(mth, block).replace("*/", "*\\/"); mth.addWarn("Code restructure failed: missing block: " + block + ", code lost:" + blockCode); } } From 58722d372e739cd8a0fbc897e55e035358349123 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 12 May 2020 22:51:38 +0100 Subject: [PATCH 39/55] fix: don't generate string concatenation without assign to variable --- .../core/dex/visitors/InitCodeVariables.java | 2 +- .../core/dex/visitors/SimplifyVisitor.java | 12 +++++++ .../others/TestStringConcatWithoutResult.java | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatWithoutResult.java diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java index 944939995..9df3b6897 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java @@ -52,7 +52,7 @@ public class InitCodeVariables extends AbstractVisitor { } } - private static void initCodeVar(SSAVar ssaVar) { + public static void initCodeVar(SSAVar ssaVar) { if (ssaVar.isCodeVarSet()) { return; } 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 09ae30f4a..8b459f587 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 @@ -401,6 +401,7 @@ public class SimplifyVisitor extends AbstractVisitor { concatInsn.copyAttributesFrom(toStrInsn); concatInsn.remove(AFlag.DONT_GENERATE); concatInsn.remove(AFlag.REMOVE); + checkResult(mth, concatInsn); return concatInsn; } catch (Exception e) { LOG.warn("Can't convert string concatenation: {} insn: {}", mth, toStrInsn, e); @@ -408,6 +409,17 @@ public class SimplifyVisitor extends AbstractVisitor { return null; } + /* String concat without assign to variable will cause compilation error */ + private static void checkResult(MethodNode mth, InsnNode concatInsn) { + if (concatInsn.getResult() == null) { + RegisterArg resArg = InsnArg.reg(0, ArgType.STRING); + SSAVar ssaVar = mth.makeNewSVar(resArg); + InitCodeVariables.initCodeVar(ssaVar); + ssaVar.setType(ArgType.STRING); + concatInsn.setResult(resArg); + } + } + /** * Remove and unbind all instructions with StringBuilder */ diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatWithoutResult.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatWithoutResult.java new file mode 100644 index 000000000..9e2b8ecd8 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestStringConcatWithoutResult.java @@ -0,0 +1,32 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestStringConcatWithoutResult extends IntegrationTest { + private static final Logger LOG = LoggerFactory.getLogger(TestStringConcatWithoutResult.class); + + public static class TestCls { + public static final boolean LOG_DEBUG = false; + + public void test(int i) { + String msg = "Input arg value: " + i; + if (LOG_DEBUG) { + LOG.debug(msg); + } + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne(" = \"Input arg value: \" + i;"); + } +} From 09e267f8bcf88daa262a6f968a674af48d1b8caf Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 17 May 2020 14:58:34 +0100 Subject: [PATCH 40/55] fix: resolve generic types in method arguments (#913) --- .../dex/attributes/nodes/GenericInfoAttr.java | 7 +++ .../java/jadx/core/dex/nodes/MethodNode.java | 6 ++ .../dex/visitors/MethodInvokeVisitor.java | 20 +++--- .../dex/visitors/OverrideMethodVisitor.java | 61 +++++++++++++++++++ .../visitors/typeinference/TypeUpdate.java | 5 +- 5 files changed, 89 insertions(+), 10 deletions(-) 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 5466919a9..4690c2cc3 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 @@ -1,5 +1,7 @@ package jadx.core.dex.attributes.nodes; +import java.util.Arrays; + import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; import jadx.core.dex.instructions.args.ArgType; @@ -28,4 +30,9 @@ public class GenericInfoAttr implements IAttribute { public AType getType() { return AType.GENERIC_INFO; } + + @Override + public String toString() { + return "GenericInfoAttr{" + Arrays.toString(genericTypes) + ", explicit=" + explicit + '}'; + } } 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 edca171ec..957fa0d89 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 @@ -274,6 +274,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return argTypes; } + public void updateArgTypes(List newArgTypes, String comment) { + this.addDebugComment(comment + ", original types: " + getArgTypes()); + this.argTypes = Collections.unmodifiableList(newArgTypes); + initArguments(newArgTypes); + } + public boolean containsGenericArgs() { return !Objects.equals(mthInfo.getArgumentsTypes(), getArgTypes()); } 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 24c5eeae7..e9401eedc 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 @@ -156,16 +156,20 @@ public class MethodInvokeVisitor extends AbstractVisitor { } private Map getTypeVarsMapping(BaseInvokeNode invokeInsn) { - MethodInfo callMth = invokeInsn.getCallMth(); - ArgType declClsType = callMth.getDeclClass().getType(); - ArgType callClsType; + ArgType declClsType = invokeInsn.getCallMth().getDeclClass().getType(); + ArgType callClsType = getClsCallType(invokeInsn, declClsType); + return root.getTypeUtils().getTypeVariablesMapping(callClsType); + } + + private ArgType getClsCallType(BaseInvokeNode invokeInsn, ArgType declClsType) { InsnArg instanceArg = invokeInsn.getInstanceArg(); if (instanceArg != null) { - callClsType = instanceArg.getType(); - } else { - callClsType = declClsType; + return instanceArg.getType(); } - return root.getTypeUtils().getTypeVariablesMapping(callClsType); + if (invokeInsn.getType() == InsnType.CONSTRUCTOR && invokeInsn.getResult() != null) { + return invokeInsn.getResult().getType(); + } + return declClsType; } private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List compilerVarTypes, List castTypes) { @@ -213,7 +217,7 @@ public class MethodInvokeVisitor extends AbstractVisitor { } if (argType.containsTypeVariable()) { ArgType resolvedType = root.getTypeUtils().replaceTypeVariablesUsingMap(argType, typeVarsMapping); - if (resolvedType == null || resolvedType.containsTypeVariable()) { + if (resolvedType == null || resolvedType.equals(argType)) { // type variables erased from method info by compiler resolvedType = mthDetails.getMethodInfo().getArgumentsTypes().get(argNum); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java index 00b6743a5..865ca37c4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -41,6 +41,7 @@ public class OverrideMethodVisitor extends AbstractVisitor { if (!overrideList.isEmpty()) { mth.addAttr(new MethodOverrideAttr(overrideList)); fixMethodReturnType(mth, overrideList, superTypes); + fixMethodArgTypes(mth, overrideList, superTypes); } } return true; @@ -105,6 +106,9 @@ public class OverrideMethodVisitor extends AbstractVisitor { private void fixMethodReturnType(MethodNode mth, List overrideList, List superTypes) { ArgType returnType = mth.getReturnType(); + if (returnType == ArgType.VOID) { + return; + } int updateCount = 0; for (IMethodDetails baseMth : overrideList) { if (updateReturnType(mth, baseMth, superTypes)) { @@ -145,4 +149,61 @@ public class OverrideMethodVisitor extends AbstractVisitor { } return false; } + + private void fixMethodArgTypes(MethodNode mth, List overrideList, List superTypes) { + for (IMethodDetails baseMth : overrideList) { + updateArgTypes(mth, baseMth, superTypes); + } + } + + private void updateArgTypes(MethodNode mth, IMethodDetails baseMth, List superTypes) { + List mthArgTypes = mth.getArgTypes(); + List baseArgTypes = baseMth.getArgTypes(); + if (mthArgTypes.equals(baseArgTypes)) { + return; + } + int argCount = mthArgTypes.size(); + if (argCount != baseArgTypes.size()) { + return; + } + boolean changed = false; + List newArgTypes = new ArrayList<>(argCount); + for (int argNum = 0; argNum < argCount; argNum++) { + ArgType newType = updateArgType(mth, baseMth, superTypes, argNum); + if (newType != null) { + changed = true; + newArgTypes.add(newType); + } else { + newArgTypes.add(mthArgTypes.get(argNum)); + } + } + if (changed) { + mth.updateArgTypes(newArgTypes, "Method arguments types fixed to match base method"); + } + } + + private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, List superTypes, int argNum) { + ArgType arg = mth.getArgTypes().get(argNum); + ArgType baseArg = baseMth.getArgTypes().get(argNum); + if (arg.equals(baseArg)) { + return null; + } + if (!baseArg.containsTypeVariable()) { + return null; + } + TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); + ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); + for (ArgType superType : superTypes) { + TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); + if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { + ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg); + if (targetArgType != null + && !targetArgType.containsTypeVariable() + && !targetArgType.equals(arg)) { + return targetArgType; + } + } + } + return null; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 47bf57141..fa18c5a3e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -12,9 +12,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; +import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.PrimitiveType; @@ -277,11 +277,12 @@ public final class TypeUpdate { registry.put(InsnType.NOT, this::suggestAllSameListener); registry.put(InsnType.CHECK_CAST, this::checkCastListener); registry.put(InsnType.INVOKE, this::invokeListener); + registry.put(InsnType.CONSTRUCTOR, this::invokeListener); return registry; } private TypeUpdateResult invokeListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { - InvokeNode invoke = (InvokeNode) insn; + BaseInvokeNode invoke = (BaseInvokeNode) insn; if (isAssign(invoke, arg)) { // TODO: implement backward type propagation (from result to instance) return SAME; From 0d69e0ac971f53fd89dd15db16d7c5dd21c7fbb5 Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 29 Apr 2020 17:24:44 +0100 Subject: [PATCH 41/55] refactor: use own dex parser instead deprecated dx lib --- .gitignore | 1 - build.gradle | 3 + jadx-cli/build.gradle | 4 + jadx-cli/src/main/java/jadx/cli/JadxCLI.java | 32 +- jadx-core/build.gradle | 11 +- .../main/java/jadx/api/JadxDecompiler.java | 80 ++- .../src/main/java/jadx/api/JavaField.java | 2 +- .../src/main/java/jadx/api/JavaMethod.java | 4 +- .../main/java/jadx/api/ResourcesLoader.java | 8 +- jadx-core/src/main/java/jadx/core/Consts.java | 10 +- jadx-core/src/main/java/jadx/core/Jadx.java | 174 +++-- .../java/jadx/core/clsp/ConvertToClsSet.java | 48 +- .../java/jadx/core/codegen/AnnotationGen.java | 143 +++-- .../main/java/jadx/core/codegen/ClassGen.java | 58 +- .../main/java/jadx/core/codegen/InsnGen.java | 46 +- .../java/jadx/core/codegen/MethodGen.java | 37 +- .../java/jadx/core/codegen/RegionGen.java | 8 +- .../jadx/core/codegen/json/JsonCodeGen.java | 2 +- .../java/jadx/core/deobf/Deobfuscator.java | 40 +- .../java/jadx/core/dex/attributes/AType.java | 1 - .../jadx/core/dex/attributes/AttrNode.java | 4 +- .../core/dex/attributes/AttributeStorage.java | 4 +- .../core/dex/attributes/EmptyAttrStorage.java | 4 +- .../parser => attributes}/FieldInitAttr.java | 14 +- .../core/dex/attributes/IAttributeNode.java | 4 +- .../attributes/annotations/Annotation.java | 47 -- .../annotations/AnnotationsList.java | 39 +- .../annotations/MethodParameters.java | 17 +- .../nodes/LocalVarsDebugInfoAttr.java | 8 +- .../attributes/nodes/RegDebugInfoAttr.java | 5 - .../java/jadx/core/dex/info/AccessInfo.java | 60 +- .../java/jadx/core/dex/info/ClassInfo.java | 8 - .../java/jadx/core/dex/info/ConstStorage.java | 18 +- .../java/jadx/core/dex/info/FieldInfo.java | 19 +- .../java/jadx/core/dex/info/InfoStorage.java | 24 - .../java/jadx/core/dex/info/MethodInfo.java | 26 +- .../jadx/core/dex/instructions/ArithNode.java | 7 +- ...{FillArrayNode.java => FillArrayData.java} | 92 +-- .../core/dex/instructions/FillArrayInsn.java | 67 ++ .../jadx/core/dex/instructions/IfNode.java | 7 +- .../core/dex/instructions/InsnDecoder.java | 600 ++++++------------ .../jadx/core/dex/instructions/InsnType.java | 8 +- .../core/dex/instructions/InvokeNode.java | 16 +- .../core/dex/instructions/SwitchData.java | 41 ++ .../core/dex/instructions/SwitchInsn.java | 193 ++++++ .../core/dex/instructions/SwitchNode.java | 142 ----- .../core/dex/instructions/args/ArgType.java | 7 +- .../core/dex/instructions/args/InsnArg.java | 20 +- .../java/jadx/core/dex/nodes/ClassNode.java | 155 ++--- .../java/jadx/core/dex/nodes/DexNode.java | 310 --------- .../java/jadx/core/dex/nodes/FieldNode.java | 28 +- .../java/jadx/core/dex/nodes/IDexNode.java | 6 +- .../java/jadx/core/dex/nodes/InsnNode.java | 11 +- .../java/jadx/core/dex/nodes/MethodNode.java | 220 ++----- .../java/jadx/core/dex/nodes/RootNode.java | 239 +++++-- .../dex/nodes/parser/AnnotationsParser.java | 112 ---- .../core/dex/nodes/parser/EncValueParser.java | 132 ---- .../dex/nodes/parser/SignatureParser.java | 10 +- .../dex/nodes/parser/StaticValuesParser.java | 28 - .../jadx/core/dex/trycatch/TryCatchBlock.java | 5 +- .../dex/visitors/AttachTryCatchVisitor.java | 135 ++++ .../jadx/core/dex/visitors/ClassModifier.java | 11 +- .../core/dex/visitors/ConstructorVisitor.java | 4 +- .../dex/visitors/DependencyCollector.java | 76 ++- .../core/dex/visitors/DepthTraversal.java | 4 - .../jadx/core/dex/visitors/EnumVisitor.java | 18 +- .../core/dex/visitors/ExtractFieldInit.java | 8 +- .../core/dex/visitors/FixAccessModifiers.java | 9 +- .../dex/visitors/GenericTypesVisitor.java | 2 +- .../dex/visitors/MethodInlineVisitor.java | 5 +- .../jadx/core/dex/visitors/ModVisitor.java | 53 +- .../visitors/ProcessInstructionsVisitor.java | 153 +++++ .../jadx/core/dex/visitors/ReSugarCode.java | 35 +- .../jadx/core/dex/visitors/RenameVisitor.java | 12 +- .../debuginfo/DebugInfoApplyVisitor.java | 10 +- .../debuginfo/DebugInfoAttachVisitor.java | 163 +++++ .../debuginfo/DebugInfoParseVisitor.java | 119 ---- .../core/dex/visitors/debuginfo/LocalVar.java | 120 ---- .../regions/ProcessTryCatchRegions.java | 2 +- .../dex/visitors/regions/RegionMaker.java | 11 +- .../jadx/core/export/ExportGradleProject.java | 15 +- .../main/java/jadx/core/utils/AsmUtils.java | 22 - .../java/jadx/core/utils/ErrorsCounter.java | 8 +- .../main/java/jadx/core/utils/InsnUtils.java | 46 +- .../main/java/jadx/core/utils/SmaliUtils.java | 15 +- .../utils/android/AndroidResourcesUtils.java | 27 +- .../java/jadx/core/utils/files/DexFile.java | 40 -- .../java/jadx/core/utils/files/InputFile.java | 233 ------- .../java/jadx/core/utils/files/JavaToDex.java | 74 --- .../java/jadx/api/JadxDecompilerTest.java | 33 +- .../jadx/core/dex/info/AccessInfoTest.java | 11 +- .../typeinference/TypeCompareTest.java | 2 +- .../jadx/core/utils/files/InputFileTest.java | 37 -- .../java/jadx/tests/api/IntegrationTest.java | 19 +- .../jadx/tests/external/BaseExternalTest.java | 2 +- .../tests/functional/JadxClasspathTest.java | 2 +- .../integration/others/TestLoopInTry2.java | 52 -- .../integration/trycatch/TestFinally.java | 1 + .../trycatch/TestTryCatchFinally8.java | 2 + .../trycatch/TestTryCatchLastInsn.java | 14 +- .../src/main/java/jadx/gui/JadxWrapper.java | 14 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 1 + .../main/java/jadx/gui/ui/RenameDialog.java | 10 +- jadx-plugins/jadx-dex-input/build.gradle | 9 + .../jadx/plugins/input/dex/DexException.java | 13 + .../jadx/plugins/input/dex/DexFileLoader.java | 85 +++ .../plugins/input/dex/DexInputPlugin.java | 21 + .../jadx/plugins/input/dex/DexLoadResult.java | 35 + .../jadx/plugins/input/dex/DexReader.java | 96 +++ .../plugins/input/dex/insns/DexInsnData.java | 208 ++++++ .../input/dex/insns/DexInsnFormat.java | 451 +++++++++++++ .../plugins/input/dex/insns/DexInsnInfo.java | 350 ++++++++++ .../plugins/input/dex/insns/DexOpcodes.java | 233 +++++++ .../dex/insns/payloads/DexArrayPayload.java | 31 + .../dex/insns/payloads/DexSwitchPayload.java | 31 + .../input/dex/sections/DexClassData.java | 193 ++++++ .../input/dex/sections/DexCodeReader.java | 189 ++++++ .../plugins/input/dex/sections/DexConsts.java | 12 + .../input/dex/sections/DexFieldData.java | 77 +++ .../plugins/input/dex/sections/DexHeader.java | 97 +++ .../input/dex/sections/DexMethodData.java | 133 ++++ .../input/dex/sections/SectionReader.java | 245 +++++++ .../annotations/AnnotationsParser.java | 167 +++++ .../sections/annotations/DexAnnotation.java | 39 ++ .../annotations/EncodedValueParser.java | 117 ++++ .../dex/sections/debuginfo/DebugInfo.java | 33 + .../sections}/debuginfo/DebugInfoParser.java | 148 +++-- .../dex/sections/debuginfo/LocalVar.java | 107 ++++ .../input/dex/sections/trycatch/DexCatch.java | 30 + .../dex/sections/trycatch/DexTryData.java | 32 + .../input/dex/smali/InsnFormatter.java | 5 + .../input/dex/smali/InsnFormatterInfo.java | 49 ++ .../input/dex/smali/SmaliCodeWriter.java | 77 +++ .../input/dex/smali/SmaliInsnFormat.java | 155 +++++ .../plugins/input/dex/smali/SmaliPrinter.java | 36 ++ .../jadx/plugins/input/dex/utils/Leb128.java | 45 ++ .../jadx/plugins/input/dex/utils/MUtf8.java | 40 ++ .../jadx/plugins/input/dex/utils/Utils.java | 23 + .../services/jadx.api.plugins.JadxPlugin | 1 + .../plugins/input/dex/DexInputPluginTest.java | 73 +++ .../plugins/input/dex/utils/SmaliUtils.java | 47 ++ .../resources/samples/app-with-fake-dex.apk | Bin 0 -> 10030 bytes .../src/test/resources/samples/hello.dex | Bin 0 -> 824 bytes .../src/test/resources/samples/test.smali | 16 + jadx-plugins/jadx-java-convert/build.gradle | 11 + .../jadx-java-convert}/lib/dx-1.16.jar | Bin .../input/javaconvert/ConvertResult.java | 67 ++ .../input/javaconvert/D8Converter.java | 53 ++ .../input/javaconvert/DxConverter.java | 49 ++ .../input/javaconvert/JavaConvertLoader.java | 52 ++ .../input/javaconvert/JavaConvertPlugin.java | 38 ++ .../services/jadx.api.plugins.JadxPlugin | 1 + jadx-plugins/jadx-plugins-api/build.gradle | 3 + .../java/jadx/api/plugins/JadxPlugin.java | 5 + .../java/jadx/api/plugins/JadxPluginInfo.java | 30 + .../jadx/api/plugins/JadxPluginManager.java | 44 ++ .../api/plugins/input/JadxInputPlugin.java | 11 + .../api/plugins/input/data/AccessFlags.java | 91 +++ .../plugins/input/data/AccessFlagsScope.java | 5 + .../jadx/api/plugins/input/data/ICatch.java | 9 + .../api/plugins/input/data/IClassData.java | 35 + .../api/plugins/input/data/ICodeReader.java | 25 + .../api/plugins/input/data/IDebugInfo.java | 14 + .../api/plugins/input/data/IFieldData.java | 17 + .../api/plugins/input/data/ILoadResult.java | 10 + .../api/plugins/input/data/ILocalVar.java | 18 + .../api/plugins/input/data/IMethodData.java | 30 + .../api/plugins/input/data/IResourceData.java | 4 + .../jadx/api/plugins/input/data/ITry.java | 9 + .../annotations/AnnotationVisibility.java | 7 + .../input/data/annotations/EncodedType.java | 20 + .../input/data/annotations/EncodedValue.java | 33 + .../input/data/annotations/IAnnotation.java | 18 + .../input/data/impl/EmptyLoadResult.java | 25 + .../api/plugins/input/insns/InsnData.java | 41 ++ .../plugins/input/insns/InsnIndexType.java | 9 + .../jadx/api/plugins/input/insns/Opcode.java | 193 ++++++ .../input/insns/custom/IArrayPayload.java | 9 + .../input/insns/custom/ICustomPayload.java | 4 + .../input/insns/custom/ISwitchPayload.java | 9 + settings.gradle | 4 + 181 files changed, 6779 insertions(+), 3072 deletions(-) rename jadx-core/src/main/java/jadx/core/dex/{nodes/parser => attributes}/FieldInitAttr.java (75%) delete mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java rename jadx-core/src/main/java/jadx/core/dex/instructions/{FillArrayNode.java => FillArrayData.java} (54%) create mode 100644 jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java delete mode 100644 jadx-core/src/main/java/jadx/core/utils/AsmUtils.java delete mode 100644 jadx-core/src/main/java/jadx/core/utils/files/DexFile.java delete mode 100644 jadx-core/src/main/java/jadx/core/utils/files/InputFile.java delete mode 100644 jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java delete mode 100644 jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java delete mode 100644 jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java create mode 100644 jadx-plugins/jadx-dex-input/build.gradle create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java rename {jadx-core/src/main/java/jadx/core/dex/visitors => jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections}/debuginfo/DebugInfoParser.java (56%) create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java create mode 100644 jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin create mode 100644 jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java create mode 100644 jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java create mode 100644 jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk create mode 100644 jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex create mode 100644 jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali create mode 100644 jadx-plugins/jadx-java-convert/build.gradle rename {jadx-core => jadx-plugins/jadx-java-convert}/lib/dx-1.16.jar (100%) create mode 100644 jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java create mode 100644 jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java create mode 100644 jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java create mode 100644 jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java create mode 100644 jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java create mode 100644 jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin create mode 100644 jadx-plugins/jadx-plugins-api/build.gradle create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java create mode 100644 jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java diff --git a/.gitignore b/.gitignore index 34d09155e..9d20058a1 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,6 @@ jadx-output/ *-tmp/ **/tmp/ -*.dex *.class *.dump *.log diff --git a/build.gradle b/build.gradle index 9b32030fe..c8fa053e2 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,7 @@ allprojects { dependencies { compile 'org.slf4j:slf4j-api:1.7.30' + compileOnly 'org.jetbrains:annotations:19.0.0' testCompile 'ch.qos.logback:logback-classic:1.2.3' testCompile 'org.hamcrest:hamcrest-library:2.2' @@ -44,6 +45,7 @@ allprojects { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2' testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1' + testCompileOnly 'org.jetbrains:annotations:19.0.0' } test { @@ -86,6 +88,7 @@ spotless { include 'jadx-cli/src/**/java/**/*.java' include 'jadx-core/src/**/java/**/*.java' include 'jadx-gui/src/**/java/**/*.java' + include 'jadx-plugins/**/java/**/*.java' } importOrderFile 'config/code-formatter/eclipse.importorder' diff --git a/jadx-cli/build.gradle b/jadx-cli/build.gradle index 7fec42de9..25eeed8ec 100644 --- a/jadx-cli/build.gradle +++ b/jadx-cli/build.gradle @@ -4,6 +4,10 @@ plugins { dependencies { compile(project(':jadx-core')) + + runtime(project(':jadx-plugins:jadx-dex-input')) + runtime(project(':jadx-plugins:jadx-java-convert')) + compile 'com.beust:jcommander:1.78' compile 'ch.qos.logback:logback-classic:1.2.3' } diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java index 4691991a7..ef876cc37 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java @@ -17,8 +17,11 @@ public class JadxCLI { try { JadxCLIArgs jadxArgs = new JadxCLIArgs(); if (jadxArgs.processArgs(args)) { - result = processAndSave(jadxArgs); + result = processAndSave(jadxArgs.toJadxArgs()); } + } catch (JadxArgsValidateException e) { + LOG.error("Incorrect arguments: {}", e.getMessage()); + result = 1; } catch (Exception e) { LOG.error("jadx error: {}", e.getMessage(), e); result = 1; @@ -28,23 +31,18 @@ public class JadxCLI { } } - static int processAndSave(JadxCLIArgs inputArgs) { - JadxArgs args = inputArgs.toJadxArgs(); - args.setCodeCache(new NoOpCodeCache()); - JadxDecompiler jadx = new JadxDecompiler(args); - try { + static int processAndSave(JadxArgs jadxArgs) { + jadxArgs.setCodeCache(new NoOpCodeCache()); + try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) { jadx.load(); - } catch (JadxArgsValidateException e) { - LOG.error("Incorrect arguments: {}", e.getMessage()); - return 1; - } - jadx.save(); - int errorsCount = jadx.getErrorsCount(); - if (errorsCount != 0) { - jadx.printErrorsReport(); - LOG.error("finished with errors, count: {}", errorsCount); - } else { - LOG.info("done"); + jadx.save(); + int errorsCount = jadx.getErrorsCount(); + if (errorsCount != 0) { + jadx.printErrorsReport(); + LOG.error("finished with errors, count: {}", errorsCount); + } else { + LOG.info("done"); + } } return 0; } diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index 44ffadae7..8bc7c595b 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -1,11 +1,13 @@ +plugins { + id 'java-library' +} + dependencies { runtime files('clsp-data/android-29-clst.jar') runtime files('clsp-data/android-29-res.jar') - compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53) + api(project(':jadx-plugins:jadx-plugins-api')) - compile 'org.ow2.asm:asm:8.0.1' - compile 'org.jetbrains:annotations:19.0.0' compile 'com.google.code.gson:gson:2.8.6' compile 'org.smali:baksmali:2.4.0' @@ -15,6 +17,9 @@ dependencies { compile 'com.google.guava:guava:29.0-jre' testCompile 'org.apache.commons:commons-lang3:3.9' + + testRuntime(project(':jadx-plugins:jadx-dex-input')) + testRuntime(project(':jadx-plugins:jadx-java-convert')) } test { diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index ad37fd9ed..65e1c8bd9 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -1,6 +1,8 @@ package jadx.api; +import java.io.Closeable; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -17,6 +19,10 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.JadxPlugin; +import jadx.api.plugins.JadxPluginManager; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; import jadx.core.Jadx; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.nodes.LineAttrNode; @@ -26,8 +32,8 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.SaveCode; import jadx.core.export.ExportGradleProject; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.utils.files.InputFile; import jadx.core.xmlgen.BinaryXMLParser; import jadx.core.xmlgen.ResourcesSaver; @@ -39,10 +45,10 @@ import jadx.core.xmlgen.ResourcesSaver; * JadxArgs args = new JadxArgs(); * args.getInputFiles().add(new File("test.apk")); * args.setOutDir(new File("jadx-test-output")); - * - * JadxDecompiler jadx = new JadxDecompiler(args); - * jadx.load(); - * jadx.save(); + * try (JadxDecompiler jadx = new JadxDecompiler(args)) { + * jadx.load(); + * jadx.save(); + * } * * *

@@ -56,11 +62,12 @@ import jadx.core.xmlgen.ResourcesSaver; * * */ -public final class JadxDecompiler { +public final class JadxDecompiler implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class); private JadxArgs args; - private List inputFiles; + private JadxPluginManager pluginManager = new JadxPluginManager(); + private List loadedInputs = new ArrayList<>(); private RootNode root; private List classes; @@ -68,9 +75,9 @@ public final class JadxDecompiler { private BinaryXMLParser xmlParser; - private Map classesMap = new ConcurrentHashMap<>(); - private Map methodsMap = new ConcurrentHashMap<>(); - private Map fieldsMap = new ConcurrentHashMap<>(); + private final Map classesMap = new ConcurrentHashMap<>(); + private final Map methodsMap = new ConcurrentHashMap<>(); + private final Map fieldsMap = new ConcurrentHashMap<>(); public JadxDecompiler() { this(new JadxArgs()); @@ -84,16 +91,23 @@ public final class JadxDecompiler { reset(); JadxArgsValidator.validate(args); LOG.info("loading ..."); - - inputFiles = loadFiles(args.getInputFiles()); + loadInputFiles(); root = new RootNode(args); - root.load(inputFiles); + root.loadClasses(loadedInputs); root.initClassPath(); root.loadResources(getResources()); root.initPasses(); } + private void loadInputFiles() { + loadedInputs.clear(); + List inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath); + for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { + loadedInputs.add(inputPlugin.loadFiles(inputPaths)); + } + } + private void reset() { root = null; classes = null; @@ -103,27 +117,34 @@ public final class JadxDecompiler { classesMap.clear(); methodsMap.clear(); fieldsMap.clear(); + + closeInputs(); + } + + private void closeInputs() { + loadedInputs.forEach(load -> { + try { + load.close(); + } catch (Exception e) { + LOG.error("Failed to close input", e); + } + }); + loadedInputs.clear(); + } + + @Override + public void close() { + reset(); + } + + public void registerPlugin(JadxPlugin plugin) { + pluginManager.register(plugin); } public static String getVersion() { return Jadx.getVersion(); } - private List loadFiles(List files) { - if (files.isEmpty()) { - throw new JadxRuntimeException("Empty file list"); - } - List filesList = new ArrayList<>(); - for (File file : files) { - try { - InputFile.addFilesFrom(file, filesList, args.isSkipSources()); - } catch (Exception e) { - throw new JadxRuntimeException("Error load file: " + file, e); - } - } - return filesList; - } - public void save() { save(!args.isSkipSources(), !args.isSkipResources()); } @@ -232,7 +253,7 @@ public final class JadxDecompiler { if (root == null) { return Collections.emptyList(); } - resources = new ResourcesLoader(this).load(inputFiles); + resources = new ResourcesLoader(this).load(); } return resources; } @@ -432,4 +453,5 @@ public final class JadxDecompiler { public String toString() { return "jadx decompiler " + getVersion(); } + } diff --git a/jadx-core/src/main/java/jadx/api/JavaField.java b/jadx-core/src/main/java/jadx/api/JavaField.java index cfe0dd6d4..582c66bb4 100644 --- a/jadx-core/src/main/java/jadx/api/JavaField.java +++ b/jadx-core/src/main/java/jadx/api/JavaField.java @@ -39,7 +39,7 @@ public final class JavaField implements JavaNode { } public ArgType getType() { - return ArgType.tryToResolveClassAlias(field.dex(), field.getType()); + return ArgType.tryToResolveClassAlias(field.root(), field.getType()); } @Override diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 82b5ac492..13e863fe8 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -48,12 +48,12 @@ public final class JavaMethod implements JavaNode { } List arguments = mth.getArgTypes(); return Utils.collectionMap(arguments, - type -> ArgType.tryToResolveClassAlias(mth.dex(), type)); + type -> ArgType.tryToResolveClassAlias(mth.root(), type)); } public ArgType getReturnType() { ArgType retType = mth.getReturnType(); - return ArgType.tryToResolveClassAlias(mth.dex(), retType); + return ArgType.tryToResolveClassAlias(mth.root(), retType); } public boolean isConstructor() { diff --git a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java index 152ed4dfd..22a6293b0 100644 --- a/jadx-core/src/main/java/jadx/api/ResourcesLoader.java +++ b/jadx-core/src/main/java/jadx/api/ResourcesLoader.java @@ -21,7 +21,6 @@ import jadx.core.codegen.CodeWriter; import jadx.core.utils.Utils; import jadx.core.utils.android.Res9patchStreamDecoder; import jadx.core.utils.exceptions.JadxException; -import jadx.core.utils.files.InputFile; import jadx.core.utils.files.ZipSecurity; import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResTableParser; @@ -39,10 +38,11 @@ public final class ResourcesLoader { this.jadxRef = jadxRef; } - List load(List inputFiles) { + List load() { + List inputFiles = jadxRef.getArgs().getInputFiles(); List list = new ArrayList<>(inputFiles.size()); - for (InputFile file : inputFiles) { - loadFile(list, file.getFile()); + for (File file : inputFiles) { + loadFile(list, file); } return list; } diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index ab35e8a2e..efe038512 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -11,11 +11,11 @@ public class Consts { public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder"; - public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation."; - public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature"; - public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass"; - public static final String DALVIK_THROWS = "dalvik.annotation.Throws"; - public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault"; + 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 DEFAULT_PACKAGE_NAME = "defpackage"; public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass"; diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 84dd65be2..51846927a 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -11,13 +11,40 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; -import jadx.core.dex.visitors.*; +import jadx.core.dex.visitors.AttachMethodDetails; +import jadx.core.dex.visitors.AttachTryCatchVisitor; +import jadx.core.dex.visitors.ClassModifier; +import jadx.core.dex.visitors.ConstInlineVisitor; +import jadx.core.dex.visitors.ConstructorVisitor; +import jadx.core.dex.visitors.DeboxingVisitor; +import jadx.core.dex.visitors.DependencyCollector; +import jadx.core.dex.visitors.DotGraphVisitor; +import jadx.core.dex.visitors.EnumVisitor; +import jadx.core.dex.visitors.ExtractFieldInit; +import jadx.core.dex.visitors.FallbackModeVisitor; +import jadx.core.dex.visitors.FixAccessModifiers; +import jadx.core.dex.visitors.GenericTypesVisitor; +import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.dex.visitors.InitCodeVariables; +import jadx.core.dex.visitors.MarkFinallyVisitor; +import jadx.core.dex.visitors.MethodInlineVisitor; +import jadx.core.dex.visitors.MethodInvokeVisitor; +import jadx.core.dex.visitors.ModVisitor; +import jadx.core.dex.visitors.MoveInlineVisitor; +import jadx.core.dex.visitors.OverrideMethodVisitor; +import jadx.core.dex.visitors.PrepareForCodeGen; +import jadx.core.dex.visitors.ProcessAnonymous; +import jadx.core.dex.visitors.ProcessInstructionsVisitor; +import jadx.core.dex.visitors.ReSugarCode; +import jadx.core.dex.visitors.RenameVisitor; +import jadx.core.dex.visitors.ShadowFieldVisitor; +import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; import jadx.core.dex.visitors.blocksmaker.BlockFinish; import jadx.core.dex.visitors.blocksmaker.BlockProcessor; import jadx.core.dex.visitors.blocksmaker.BlockSplitter; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; -import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor; +import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor; import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.IfRegionVisitor; @@ -41,73 +68,88 @@ public class Jadx { } } + public static List getFallbackPassesList() { + List passes = new ArrayList<>(3); + passes.add(new AttachTryCatchVisitor()); + passes.add(new ProcessInstructionsVisitor()); + passes.add(new FallbackModeVisitor()); + return passes; + } + public static List getPassesList(JadxArgs args) { - List passes = new ArrayList<>(); if (args.isFallbackMode()) { - passes.add(new FallbackModeVisitor()); - } else { - if (args.isDebugInfo()) { - passes.add(new DebugInfoParseVisitor()); - } - passes.add(new BlockSplitter()); - if (args.isRawCFGOutput()) { - passes.add(DotGraphVisitor.dumpRaw()); - } - passes.add(new BlockProcessor()); - passes.add(new BlockExceptionHandler()); - passes.add(new BlockFinish()); - - passes.add(new AttachMethodDetails()); - passes.add(new OverrideMethodVisitor()); - - passes.add(new SSATransform()); - passes.add(new MoveInlineVisitor()); - passes.add(new ConstructorVisitor()); - passes.add(new InitCodeVariables()); - passes.add(new MarkFinallyVisitor()); - passes.add(new ConstInlineVisitor()); - passes.add(new TypeInferenceVisitor()); - if (args.isDebugInfo()) { - passes.add(new DebugInfoApplyVisitor()); - } - - passes.add(new GenericTypesVisitor()); - passes.add(new ShadowFieldVisitor()); - passes.add(new DeboxingVisitor()); - passes.add(new ModVisitor()); - passes.add(new CodeShrinkVisitor()); - passes.add(new ReSugarCode()); - if (args.isCfgOutput()) { - passes.add(DotGraphVisitor.dump()); - } - - passes.add(new RegionMakerVisitor()); - passes.add(new IfRegionVisitor()); - passes.add(new ReturnVisitor()); - passes.add(new CleanRegions()); - - passes.add(new CodeShrinkVisitor()); - passes.add(new MethodInvokeVisitor()); - passes.add(new SimplifyVisitor()); - passes.add(new CheckRegions()); - - passes.add(new EnumVisitor()); - passes.add(new ExtractFieldInit()); - passes.add(new FixAccessModifiers()); - passes.add(new ProcessAnonymous()); - passes.add(new ClassModifier()); - passes.add(new MethodInlineVisitor()); - passes.add(new LoopRegionVisitor()); - - passes.add(new ProcessVariables()); - passes.add(new PrepareForCodeGen()); - if (args.isCfgOutput()) { - passes.add(DotGraphVisitor.dumpRegions()); - } - - passes.add(new DependencyCollector()); - passes.add(new RenameVisitor()); + return getFallbackPassesList(); } + + List passes = new ArrayList<>(); + if (args.isDebugInfo()) { + passes.add(new DebugInfoAttachVisitor()); + } + passes.add(new AttachTryCatchVisitor()); + passes.add(new ProcessInstructionsVisitor()); + + passes.add(new BlockSplitter()); + if (args.isRawCFGOutput()) { + passes.add(DotGraphVisitor.dumpRaw()); + } + passes.add(new BlockProcessor()); + passes.add(new BlockExceptionHandler()); + passes.add(new BlockFinish()); + + passes.add(new AttachMethodDetails()); + passes.add(new OverrideMethodVisitor()); + + passes.add(new SSATransform()); + passes.add(new MoveInlineVisitor()); + passes.add(new ConstructorVisitor()); + passes.add(new InitCodeVariables()); + passes.add(new MarkFinallyVisitor()); + passes.add(new ConstInlineVisitor()); + passes.add(new TypeInferenceVisitor()); + if (args.isRawCFGOutput()) { + passes.add(DotGraphVisitor.dumpRaw()); + } + if (args.isDebugInfo()) { + passes.add(new DebugInfoApplyVisitor()); + } + + passes.add(new GenericTypesVisitor()); + passes.add(new ShadowFieldVisitor()); + passes.add(new DeboxingVisitor()); + passes.add(new ModVisitor()); + passes.add(new CodeShrinkVisitor()); + passes.add(new ReSugarCode()); + if (args.isCfgOutput()) { + passes.add(DotGraphVisitor.dump()); + } + + passes.add(new RegionMakerVisitor()); + passes.add(new IfRegionVisitor()); + passes.add(new ReturnVisitor()); + passes.add(new CleanRegions()); + + passes.add(new CodeShrinkVisitor()); + passes.add(new MethodInvokeVisitor()); + passes.add(new SimplifyVisitor()); + passes.add(new CheckRegions()); + + passes.add(new EnumVisitor()); + passes.add(new ExtractFieldInit()); + passes.add(new FixAccessModifiers()); + passes.add(new ProcessAnonymous()); + passes.add(new ClassModifier()); + passes.add(new MethodInlineVisitor()); + passes.add(new LoopRegionVisitor()); + + passes.add(new ProcessVariables()); + passes.add(new PrepareForCodeGen()); + if (args.isCfgOutput()) { + passes.add(DotGraphVisitor.dumpRegions()); + } + + passes.add(new DependencyCollector()); + passes.add(new RenameVisitor()); + return passes; } diff --git a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java index 95ae11ac2..a799d0619 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java @@ -1,20 +1,22 @@ package jadx.core.clsp; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; +import jadx.api.plugins.JadxPluginManager; +import jadx.api.plugins.input.JadxInputPlugin; +import jadx.api.plugins.input.data.ILoadResult; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.exceptions.DecodeException; -import jadx.core.utils.files.InputFile; /** * Utility class for convert dex or jar to jadx classes set (.jcst) @@ -26,30 +28,24 @@ public class ConvertToClsSet { LOG.info(" "); } - public static void main(String[] args) throws IOException, DecodeException { + public static void main(String[] args) throws IOException { if (args.length < 2) { usage(); System.exit(1); } - Path output = Paths.get(args[0]); + List inputPaths = Stream.of(args).map(s -> Paths.get(s)).collect(Collectors.toList()); + Path output = inputPaths.remove(0); - List inputFiles = new ArrayList<>(args.length - 1); - for (int i = 1; i < args.length; i++) { - File f = new File(args[i]); - if (f.isDirectory()) { - addFilesFromDirectory(f, inputFiles); - } else { - InputFile.addFilesFrom(f, inputFiles, false); - } - } - for (InputFile inputFile : inputFiles) { - LOG.info("Loaded: {}", inputFile.getFile()); + JadxPluginManager pluginManager = new JadxPluginManager(); + List loadedInputs = new ArrayList<>(); + for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { + loadedInputs.add(inputPlugin.loadFiles(inputPaths)); } JadxArgs jadxArgs = new JadxArgs(); jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class)); RootNode root = new RootNode(jadxArgs); - root.load(inputFiles); + root.loadClasses(loadedInputs); ClsSet set = new ClsSet(root); set.loadFrom(root); @@ -57,22 +53,4 @@ public class ConvertToClsSet { LOG.info("Output: {}, file size: {}B", output, output.toFile().length()); LOG.info("done"); } - - private static void addFilesFromDirectory(File dir, List inputFiles) { - File[] files = dir.listFiles(); - if (files == null) { - return; - } - for (File file : files) { - if (file.isDirectory()) { - addFilesFromDirectory(file, inputFiles); - } else { - try { - InputFile.addFilesFrom(file, inputFiles, false); - } catch (Exception e) { - LOG.warn("Skip file: {}, load error: {}", file, e.getMessage()); - } - } - } - } } 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 01f4f7889..2ec79bb09 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -7,10 +7,12 @@ import java.util.Map.Entry; 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.core.Consts; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttributeNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.info.FieldInfo; @@ -18,6 +20,7 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -52,7 +55,7 @@ public class AnnotationGen { if (aList == null || aList.isEmpty()) { return; } - for (Annotation a : aList.getAll()) { + for (IAnnotation a : aList.getAll()) { formatAnnotation(code, a); code.add(' '); } @@ -63,7 +66,7 @@ public class AnnotationGen { if (aList == null || aList.isEmpty()) { return; } - for (Annotation a : aList.getAll()) { + for (IAnnotation a : aList.getAll()) { String aCls = a.getAnnotationClass(); if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) { code.startLine(); @@ -72,20 +75,20 @@ public class AnnotationGen { } } - private void formatAnnotation(CodeWriter code, Annotation a) { + private void formatAnnotation(CodeWriter code, IAnnotation a) { code.add('@'); - ClassNode annCls = cls.dex().resolveClass(a.getType()); + ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass()); if (annCls != null) { classGen.useClass(code, annCls); } else { - classGen.useType(code, a.getType()); + classGen.useClass(code, a.getAnnotationClass()); } - Map vl = a.getValues(); + Map vl = a.getValues(); if (!vl.isEmpty()) { code.add('('); - for (Iterator> it = vl.entrySet().iterator(); it.hasNext();) { - Entry e = it.next(); + for (Iterator> it = vl.entrySet().iterator(); it.hasNext();) { + Entry e = it.next(); String paramName = getParamName(annCls, e.getKey()); if (paramName.equals("value") && vl.size() == 1) { // don't add "value = " if no other parameters @@ -93,7 +96,7 @@ public class AnnotationGen { code.add(paramName); code.add(" = "); } - encodeValue(code, e.getValue()); + encodeValue(cls.root(), code, e.getValue()); if (it.hasNext()) { code.add(", "); } @@ -127,66 +130,94 @@ public class AnnotationGen { } } - public Object getAnnotationDefaultValue(String name) { - Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); + public EncodedValue getAnnotationDefaultValue(String name) { + IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT); if (an != null) { - Annotation defAnnotation = (Annotation) an.getDefaultValue(); + IAnnotation defAnnotation = (IAnnotation) an.getDefaultValue().getValue(); return defAnnotation.getValues().get(name); } return null; } // TODO: refactor this boilerplate code - public void encodeValue(CodeWriter code, Object val) { - if (val == null) { + public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) { + if (encodedValue == null) { code.add("null"); return; } - if (val instanceof String) { - code.add(getStringUtils().unescapeString((String) val)); - } else if (val instanceof Integer) { - code.add(TypeGen.formatInteger((Integer) val, false)); - } else if (val instanceof Character) { - code.add(getStringUtils().unescapeChar((Character) val)); - } else if (val instanceof Boolean) { - code.add(Boolean.TRUE.equals(val) ? "true" : "false"); - } else if (val instanceof Float) { - code.add(TypeGen.formatFloat((Float) val)); - } else if (val instanceof Double) { - code.add(TypeGen.formatDouble((Double) val)); - } else if (val instanceof Long) { - code.add(TypeGen.formatLong((Long) val, false)); - } else if (val instanceof Short) { - code.add(TypeGen.formatShort((Short) val, false)); - } else if (val instanceof Byte) { - code.add(TypeGen.formatByte((Byte) val, false)); - } else if (val instanceof ArgType) { - classGen.useType(code, (ArgType) val); - code.add(".class"); - } else if (val instanceof FieldInfo) { - // must be a static field - FieldInfo field = (FieldInfo) val; - InsnGen.makeStaticFieldAccess(code, field, classGen); - } else if (val instanceof Iterable) { - code.add('{'); - Iterator it = ((Iterable) val).iterator(); - while (it.hasNext()) { - Object obj = it.next(); - encodeValue(code, obj); - if (it.hasNext()) { - code.add(", "); + Object value = encodedValue.getValue(); + switch (encodedValue.getType()) { + case ENCODED_NULL: + code.add("null"); + break; + case ENCODED_BOOLEAN: + code.add(Boolean.TRUE.equals(value) ? "true" : "false"); + break; + case ENCODED_BYTE: + code.add(TypeGen.formatByte((Byte) value, false)); + break; + case ENCODED_SHORT: + code.add(TypeGen.formatShort((Short) value, false)); + break; + case ENCODED_CHAR: + code.add(getStringUtils().unescapeChar((Character) value)); + break; + case ENCODED_INT: + code.add(TypeGen.formatInteger((Integer) value, false)); + break; + case ENCODED_LONG: + code.add(TypeGen.formatLong((Long) value, false)); + break; + case ENCODED_FLOAT: + code.add(TypeGen.formatFloat((Float) value)); + break; + case ENCODED_DOUBLE: + code.add(TypeGen.formatDouble((Double) value)); + break; + case ENCODED_STRING: + code.add(getStringUtils().unescapeString((String) value)); + break; + case ENCODED_TYPE: + classGen.useType(code, ArgType.parse((String) value)); + code.add(".class"); + break; + 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); + } else if (value instanceof FieldInfo) { + InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen); + } else { + throw new JadxRuntimeException("Unexpected field type class: " + value.getClass()); } - } - code.add('}'); - } else if (val instanceof Annotation) { - formatAnnotation(code, (Annotation) val); - } else { - // TODO: also can be method values - throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')'); + break; + case ENCODED_METHOD: + // TODO + break; + case ENCODED_ARRAY: + code.add('{'); + Iterator it = ((Iterable) value).iterator(); + while (it.hasNext()) { + EncodedValue v = (EncodedValue) it.next(); + encodeValue(cls.root(), code, v); + if (it.hasNext()) { + code.add(", "); + } + } + code.add('}'); + break; + case ENCODED_ANNOTATION: + formatAnnotation(code, (IAnnotation) value); + break; + + default: + throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')'); } } private StringUtils getStringUtils() { - return cls.dex().root().getStringUtils(); + return cls.root().getStringUtils(); } } 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 1e1bcb100..f8f6f5b59 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -13,13 +13,16 @@ import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; -import com.android.dx.rop.code.AccessFlags; - import jadx.api.ICodeInfo; 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.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.attributes.FieldInitAttr.InitType; import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField; import jadx.core.dex.attributes.nodes.JadxError; @@ -31,13 +34,11 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.GenericTypeParameter; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.dex.nodes.parser.FieldInitAttr.InitType; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.CodeGenUtils; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.Utils; @@ -122,17 +123,17 @@ public class ClassGen { public void addClassDeclaration(CodeWriter clsCode) { AccessInfo af = cls.getAccessFlags(); if (af.isInterface()) { - af = af.remove(AccessFlags.ACC_ABSTRACT) - .remove(AccessFlags.ACC_STATIC); + af = af.remove(AccessFlags.ABSTRACT) + .remove(AccessFlags.STATIC); } else if (af.isEnum()) { - af = af.remove(AccessFlags.ACC_FINAL) - .remove(AccessFlags.ACC_ABSTRACT) - .remove(AccessFlags.ACC_STATIC); + af = af.remove(AccessFlags.FINAL) + .remove(AccessFlags.ABSTRACT) + .remove(AccessFlags.STATIC); } // 'static' and 'private' modifier not allowed for top classes (not inner) if (!cls.getClassInfo().isInner()) { - af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE); + af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE); } annotationGen.addForClass(clsCode); @@ -392,15 +393,16 @@ public class ClassGen { FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null) { code.add(" = "); - if (fv.getValue() == null) { - code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); - } else { - if (fv.getValueType() == InitType.CONST) { - annotationGen.encodeValue(code, fv.getValue()); - } else if (fv.getValueType() == InitType.INSN) { - InsnGen insnGen = makeInsnGen(fv.getInsnMth()); - addInsnBody(insnGen, code, fv.getInsn()); + if (fv.getValueType() == InitType.CONST) { + EncodedValue encodedValue = fv.getEncodedValue(); + if (encodedValue.getType() == EncodedType.ENCODED_NULL) { + code.add(TypeGen.literalToString(0, f.getType(), cls, fallback)); + } else { + annotationGen.encodeValue(cls.root(), code, encodedValue); } + } else if (fv.getValueType() == InitType.INSN) { + InsnGen insnGen = makeInsnGen(fv.getInsnMth()); + addInsnBody(insnGen, code, fv.getInsn()); } } code.add(';'); @@ -425,7 +427,7 @@ public class ClassGen { EnumField f = it.next(); code.startLine(f.getField().getAlias()); ConstructorInsn constrInsn = f.getConstrInsn(); - MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth()); + MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth()); int skipCount = getEnumCtrSkipArgsCount(callMth); if (constrInsn.getArgsCount() > skipCount) { if (igen == null) { @@ -493,6 +495,10 @@ public class ClassGen { } } + public void useClass(CodeWriter code, String rawCls) { + useClass(code, ArgType.object(rawCls)); + } + public void useClass(CodeWriter code, ArgType type) { ArgType outerType = type.getOuterType(); if (outerType != null) { @@ -528,7 +534,7 @@ public class ClassGen { } public void useClass(CodeWriter code, ClassInfo classInfo) { - ClassNode classNode = cls.dex().resolveClass(classInfo); + ClassNode classNode = cls.root().resolveClass(classInfo); if (classNode != null) { useClass(code, classNode); } else { @@ -569,11 +575,11 @@ public class ClassGen { return shortName; } // don't add import if class not public (must be accessed using inheritance) - ClassNode classNode = cls.dex().resolveClass(extClsInfo); + ClassNode classNode = cls.root().resolveClass(extClsInfo); if (classNode != null && !classNode.getAccessFlags().isPublic()) { return shortName; } - if (searchCollision(cls.dex(), useCls, extClsInfo)) { + if (searchCollision(cls.root(), useCls, extClsInfo)) { return fullName; } // ignore classes from default package @@ -652,7 +658,7 @@ public class ClassGen { return false; } - private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) { + private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) { if (useCls == null) { return false; } @@ -660,7 +666,7 @@ public class ClassGen { if (useCls.getAliasShortName().equals(shortName)) { return true; } - ClassNode classNode = dex.resolveClass(useCls); + ClassNode classNode = root.resolveClass(useCls); if (classNode != null) { for (ClassNode inner : classNode.getInnerClasses()) { if (inner.getShortName().equals(shortName) @@ -669,7 +675,7 @@ public class ClassGen { } } } - return searchCollision(dex, useCls.getParentClass(), searchCls); + return searchCollision(root, useCls.getParentClass(), searchCls); } private void insertRenameInfo(CodeWriter code, ClassNode cls) { 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 2a5b3012a..654f9ace5 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -27,7 +27,7 @@ import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; -import jadx.core.dex.instructions.FillArrayNode; +import jadx.core.dex.instructions.FillArrayInsn; import jadx.core.dex.instructions.FilledNewArrayNode; import jadx.core.dex.instructions.GotoNode; import jadx.core.dex.instructions.IfNode; @@ -36,7 +36,7 @@ import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.InsnArg; @@ -152,7 +152,7 @@ public class InsnGen { private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { ClassNode pCls = mth.getParentClass(); - FieldNode fieldNode = pCls.dex().root().deepResolveField(field); + FieldNode fieldNode = pCls.root().deepResolveField(field); if (fieldNode != null) { FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); if (replace != null) { @@ -190,7 +190,7 @@ public class InsnGen { } code.add('.'); } - FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field); + FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field); if (fieldNode != null) { code.attachAnnotation(fieldNode); } @@ -264,7 +264,7 @@ public class InsnGen { switch (insn.getType()) { case CONST_STR: String str = ((ConstStringNode) insn).getString(); - code.add(mth.dex().root().getStringUtils().unescapeString(str)); + code.add(mth.root().getStringUtils().unescapeString(str)); break; case CONST_CLASS: @@ -395,7 +395,7 @@ public class InsnGen { break; case FILL_ARRAY: - FillArrayNode arrayNode = (FillArrayNode) insn; + FillArrayInsn arrayNode = (FillArrayInsn) insn; if (fallback) { String arrStr = arrayNode.dataToString(); addArg(code, insn.getArg(0)); @@ -509,15 +509,16 @@ public class InsnGen { case SWITCH: fallbackOnlyInsn(insn); - SwitchNode sw = (SwitchNode) insn; + SwitchInsn sw = (SwitchInsn) insn; code.add("switch("); addArg(code, insn.getArg(0)); code.add(") {"); code.incIndent(); - for (int i = 0; i < sw.getCasesCount(); i++) { - String key = sw.getKeys()[i].toString(); - code.startLine("case ").add(key).add(": goto "); - code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';'); + int[] keys = sw.getKeys(); + int[] targets = sw.getTargets(); + for (int i = 0; i < keys.length; i++) { + code.startLine("case ").add(Integer.toString(keys[i])).add(": goto "); + code.add(MethodGen.getLabelName(targets[i])).add(';'); } code.startLine("default: goto "); code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';'); @@ -541,6 +542,21 @@ public class InsnGen { code.add(')'); break; + case MOVE_RESULT: + fallbackOnlyInsn(insn); + code.add("move-result"); + break; + + case FILL_ARRAY_DATA: + fallbackOnlyInsn(insn); + code.add("fill-array " + insn.toString()); + break; + + case SWITCH_DATA: + fallbackOnlyInsn(insn); + code.add(insn.toString()); + break; + default: throw new CodegenException(mth, "Unknown instruction: " + insn.getType()); } @@ -550,7 +566,7 @@ public class InsnGen { * In most cases must be combined with new array instructions. * Use one by one array fill (can be replaced with System.arrayCopy) */ - private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException { + private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException { code.add("// fill-array-data instruction"); code.startLine(); List args = arrayNode.getLiteralArgs(arrayNode.getElementType()); @@ -605,7 +621,7 @@ public class InsnGen { } private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException { - ClassNode cls = mth.dex().resolveClass(insn.getClassType()); + ClassNode cls = mth.root().resolveClass(insn.getClassType()); if (cls != null && cls.isAnonymous() && !fallback) { cls.ensureProcessed(); inlineAnonymousConstructor(code, cls, insn); @@ -639,7 +655,7 @@ public class InsnGen { code.add('>'); } } - MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); generateMethodArguments(code, insn, 0, callMth); } @@ -673,7 +689,7 @@ public class InsnGen { } else { useClass(code, parent); } - MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); generateMethodArguments(code, insn, 0, callMth); code.add(' '); new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true); 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 5840b40c5..fd7456754 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -7,9 +7,10 @@ import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.annotations.EncodedValue; 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; @@ -28,7 +29,7 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.visitors.DepthTraversal; -import jadx.core.dex.visitors.FallbackModeVisitor; +import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.CodeGenUtils; import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; @@ -86,12 +87,12 @@ public class MethodGen { AccessInfo ai = mth.getAccessFlags(); // don't add 'abstract' and 'public' to methods in interface if (clsAccFlags.isInterface()) { - ai = ai.remove(AccessFlags.ACC_ABSTRACT); - ai = ai.remove(AccessFlags.ACC_PUBLIC); + ai = ai.remove(AccessFlags.ABSTRACT); + ai = ai.remove(AccessFlags.PUBLIC); } // don't add 'public' for annotations if (clsAccFlags.isAnnotation()) { - ai = ai.remove(AccessFlags.ACC_PUBLIC); + ai = ai.remove(AccessFlags.PUBLIC); } if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) { @@ -145,10 +146,10 @@ public class MethodGen { // add default value if in annotation class if (mth.getParentClass().getAccessFlags().isAnnotation()) { - Object def = annotationGen.getAnnotationDefaultValue(mth.getName()); + EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName()); if (def != null) { code.add(" default "); - annotationGen.encodeValue(code, def); + annotationGen.encodeValue(mth.root(), code, def); } } return true; @@ -269,17 +270,17 @@ public class MethodGen { } public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) { - if (mth.getInstructions() == null) { - // load original instructions - try { - mth.unload(); - mth.load(); - DepthTraversal.visit(new FallbackModeVisitor(), mth); - } catch (DecodeException e) { - LOG.error("Error reload instructions in fallback mode:", e); - code.startLine("// Can't load method instructions: " + e.getMessage()); - return; + // load original instructions + try { + mth.unload(); + mth.load(); + for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) { + DepthTraversal.visit(visitor, mth); } + } catch (DecodeException e) { + LOG.error("Error reload instructions in fallback mode:", e); + code.startLine("// Can't load method instructions: " + e.getMessage()); + return; } InsnNode[] insnArr = mth.getInstructions(); if (insnArr == null) { 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 f04002e3d..bb394fc0d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -10,11 +10,12 @@ import org.slf4j.LoggerFactory; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.InsnArg; @@ -26,7 +27,6 @@ import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.regions.Region; import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion.CaseInfo; @@ -251,7 +251,7 @@ public class RegionGen extends InsnGen { } private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException { - SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader()); + SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader()); Objects.requireNonNull(insn, "Switch insn not found in header"); InsnArg arg = insn.getArg(0); code.startLine("switch ("); @@ -288,7 +288,7 @@ public class RegionGen extends InsnGen { // print original value, sometimes replaced with incorrect field FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT); if (valueAttr != null) { - Object value = valueAttr.getValue(); + Object value = valueAttr.getEncodedValue(); if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) { code.add(" /*").add(value.toString()).add("*/"); } diff --git a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java index 47c04be57..67ccab5b8 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java @@ -68,7 +68,7 @@ public class JsonCodeGen { JsonClass jsonCls = new JsonClass(); jsonCls.setPkg(classInfo.getAliasPkg()); - jsonCls.setDex(cls.dex().getDexFile().getName()); + jsonCls.setDex(cls.getInputPath().toString()); jsonCls.setName(classInfo.getFullName()); if (classInfo.hasAlias()) { jsonCls.setAlias(classInfo.getAliasFullName()); 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 54fc89373..4dcf48988 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -12,7 +12,6 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,9 +25,9 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; public class Deobfuscator { private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class); @@ -42,8 +41,7 @@ public class Deobfuscator { public static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)"; private final JadxArgs args; - @NotNull - private final List dexNodes; + private final RootNode root; private final DeobfPresets deobfPresets; private final Map clsMap = new LinkedHashMap<>(); @@ -67,9 +65,9 @@ public class Deobfuscator { private int fldIndex = 0; private int mthIndex = 0; - public Deobfuscator(JadxArgs args, @NotNull List dexNodes, Path deobfMapFile) { + public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) { this.args = args; - this.dexNodes = dexNodes; + this.root = root; this.minLength = args.getDeobfuscationMinLength(); this.maxLength = args.getDeobfuscationMaxLength(); @@ -109,15 +107,11 @@ public class Deobfuscator { } private void preProcess() { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - Collections.addAll(reservedClsNames, cls.getPackage().split("\\.")); - } + for (ClassNode cls : root.getClasses()) { + Collections.addAll(reservedClsNames, cls.getPackage().split("\\.")); } - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - preProcessClass(cls); - } + for (ClassNode cls : root.getClasses()) { + preProcessClass(cls); } } @@ -126,10 +120,8 @@ public class Deobfuscator { if (DEBUG) { dumpAlias(); } - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - processClass(cls); - } + for (ClassNode cls : root.getClasses()) { + processClass(cls); } postProcess(); } @@ -212,14 +204,14 @@ public class Deobfuscator { if (added) { ArgType superClass = cls.getSuperClass(); if (superClass != null) { - ClassNode superNode = cls.dex().resolveClass(superClass); + ClassNode superNode = cls.root().resolveClass(superClass); if (superNode != null) { collectClassHierarchy(superNode, collected); } } for (ArgType argType : cls.getInterfaces()) { - ClassNode interfaceNode = cls.dex().resolveClass(argType); + ClassNode interfaceNode = cls.root().resolveClass(argType); if (interfaceNode != null) { collectClassHierarchy(interfaceNode, collected); } @@ -473,7 +465,7 @@ public class Deobfuscator { return null; } } - ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name); + ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name); if (otherCls != null) { return null; } @@ -584,10 +576,8 @@ public class Deobfuscator { } private void dumpAlias() { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - dumpClassAlias(cls); - } + for (ClassNode cls : root.getClasses()) { + dumpClassAlias(cls); } } 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 a6e96428d..e9be49206 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 @@ -27,7 +27,6 @@ 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.nodes.parser.FieldInitAttr; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.SplitterBlockAttr; 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 26dc41b71..c1b28275f 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 @@ -2,7 +2,7 @@ package jadx.core.dex.attributes; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public abstract class AttrNode implements IAttributeNode { @@ -64,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode { } @Override - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { return storage.getAnnotation(cls); } 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 2d342b9d1..adcf6c631 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 @@ -8,7 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -70,7 +70,7 @@ public class AttributeStorage { return (T) attributes.get(type); } - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { AnnotationsList aList = get(AType.ANNOTATION_LIST); return aList == null ? null : aList.get(cls); } 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 3b0c06e22..e449bb498 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 @@ -3,7 +3,7 @@ package jadx.core.dex.attributes; import java.util.Collections; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public final class EmptyAttrStorage extends AttributeStorage { @@ -23,7 +23,7 @@ public final class EmptyAttrStorage extends AttributeStorage { } @Override - public Annotation getAnnotation(String cls) { + public IAnnotation getAnnotation(String cls) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java similarity index 75% rename from jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java rename to jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java index da8d6b543..7522511de 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/FieldInitAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/FieldInitAttr.java @@ -1,18 +1,16 @@ -package jadx.core.dex.nodes.parser; +package jadx.core.dex.attributes; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.IAttribute; +import jadx.api.plugins.input.data.annotations.EncodedValue; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; public class FieldInitAttr implements IAttribute { - public static final FieldInitAttr NULL_VALUE = constValue(null); + public static final FieldInitAttr NULL_VALUE = constValue(EncodedValue.NULL); public enum InitType { CONST, INSN - } private final Object value; @@ -25,7 +23,7 @@ public class FieldInitAttr implements IAttribute { this.insnMth = insnMth; } - public static FieldInitAttr constValue(Object value) { + public static FieldInitAttr constValue(EncodedValue value) { return new FieldInitAttr(InitType.CONST, value, null); } @@ -33,8 +31,8 @@ public class FieldInitAttr implements IAttribute { return new FieldInitAttr(InitType.INSN, insn, mth); } - public Object getValue() { - return value; + public EncodedValue getEncodedValue() { + return (EncodedValue) value; } public InsnNode getInsn() { 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 b6435d5b1..f227fa372 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 @@ -2,7 +2,7 @@ package jadx.core.dex.attributes; import java.util.List; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.api.plugins.input.data.annotations.IAnnotation; public interface IAttributeNode { @@ -20,7 +20,7 @@ public interface IAttributeNode { T get(AType type); - Annotation getAnnotation(String cls); + IAnnotation getAnnotation(String cls); List getAll(AType> type); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java b/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java deleted file mode 100644 index 99ad42917..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/annotations/Annotation.java +++ /dev/null @@ -1,47 +0,0 @@ -package jadx.core.dex.attributes.annotations; - -import java.util.Map; - -import jadx.core.dex.instructions.args.ArgType; - -public class Annotation { - - public enum Visibility { - BUILD, RUNTIME, SYSTEM - } - - private final Visibility visibility; - private final ArgType atype; - private final Map values; - - public Annotation(Visibility visibility, ArgType type, Map values) { - this.visibility = visibility; - this.atype = type; - this.values = values; - } - - public Visibility getVisibility() { - return visibility; - } - - public ArgType getType() { - return atype; - } - - public String getAnnotationClass() { - return atype.getObject(); - } - - public Map getValues() { - return values; - } - - public Object getDefaultValue() { - return values.get("value"); - } - - @Override - public String toString() { - return "Annotation[" + visibility + ", " + atype + ", " + values + ']'; - } -} 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 index 3278ca538..3562d2d30 100644 --- 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 @@ -1,33 +1,50 @@ package jadx.core.dex.attributes.annotations; 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.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 final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList()); - - private final Map map; - - public AnnotationsList(List anList) { - map = new HashMap<>(anList.size()); - for (Annotation a : anList) { - map.put(a.getAnnotationClass(), a); + public static void attach(ICodeNode node, List annotationList) { + AnnotationsList attrList = pack(annotationList); + if (attrList != null) { + node.addAttr(attrList); } } - public Annotation get(String className) { + @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() { + public Collection getAll() { return 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 index 2f6529128..42d7e84fc 100644 --- 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 @@ -3,16 +3,29 @@ 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(int paramCount) { - paramList = new ArrayList<>(paramCount); + public MethodParameters(List paramsList) { + this.paramList = paramsList; } public List getParamList() { 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 ad2966e84..a62dbcad4 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 @@ -2,21 +2,21 @@ package jadx.core.dex.attributes.nodes; import java.util.List; +import jadx.api.plugins.input.data.ILocalVar; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; -import jadx.core.dex.visitors.debuginfo.LocalVar; import jadx.core.utils.Utils; import static jadx.core.codegen.CodeWriter.NL; public class LocalVarsDebugInfoAttr implements IAttribute { - private final List localVars; + private final List localVars; - public LocalVarsDebugInfoAttr(List localVars) { + public LocalVarsDebugInfoAttr(List localVars) { this.localVars = localVars; } - public List getLocalVars() { + public List getLocalVars() { return localVars; } 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 531794775..3a3d8fe68 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 @@ -5,17 +5,12 @@ import java.util.Objects; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.visitors.debuginfo.LocalVar; public class RegDebugInfoAttr implements IAttribute { private final ArgType type; private final String name; - public RegDebugInfoAttr(LocalVar var) { - this(var.getType(), var.getName()); - } - public RegDebugInfoAttr(ArgType type, String name) { this.type = type; this.name = name; 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 7a7571be7..bb3f82efe 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 @@ -1,13 +1,12 @@ package jadx.core.dex.info; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.utils.exceptions.JadxRuntimeException; public class AccessInfo { - public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE; + public static final int VISIBILITY_FLAGS = AccessFlags.PUBLIC | AccessFlags.PROTECTED | AccessFlags.PRIVATE; private final int accFlags; public enum AFType { @@ -53,15 +52,15 @@ public class AccessInfo { } public boolean isPublic() { - return (accFlags & AccessFlags.ACC_PUBLIC) != 0; + return (accFlags & AccessFlags.PUBLIC) != 0; } public boolean isProtected() { - return (accFlags & AccessFlags.ACC_PROTECTED) != 0; + return (accFlags & AccessFlags.PROTECTED) != 0; } public boolean isPrivate() { - return (accFlags & AccessFlags.ACC_PRIVATE) != 0; + return (accFlags & AccessFlags.PRIVATE) != 0; } public boolean isPackagePrivate() { @@ -69,59 +68,59 @@ public class AccessInfo { } public boolean isAbstract() { - return (accFlags & AccessFlags.ACC_ABSTRACT) != 0; + return (accFlags & AccessFlags.ABSTRACT) != 0; } public boolean isInterface() { - return (accFlags & AccessFlags.ACC_INTERFACE) != 0; + return (accFlags & AccessFlags.INTERFACE) != 0; } public boolean isAnnotation() { - return (accFlags & AccessFlags.ACC_ANNOTATION) != 0; + return (accFlags & AccessFlags.ANNOTATION) != 0; } public boolean isNative() { - return (accFlags & AccessFlags.ACC_NATIVE) != 0; + return (accFlags & AccessFlags.NATIVE) != 0; } public boolean isStatic() { - return (accFlags & AccessFlags.ACC_STATIC) != 0; + return (accFlags & AccessFlags.STATIC) != 0; } public boolean isFinal() { - return (accFlags & AccessFlags.ACC_FINAL) != 0; + return (accFlags & AccessFlags.FINAL) != 0; } public boolean isConstructor() { - return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0; + return (accFlags & AccessFlags.CONSTRUCTOR) != 0; } public boolean isEnum() { - return (accFlags & AccessFlags.ACC_ENUM) != 0; + return (accFlags & AccessFlags.ENUM) != 0; } public boolean isSynthetic() { - return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0; + return (accFlags & AccessFlags.SYNTHETIC) != 0; } public boolean isBridge() { - return (accFlags & AccessFlags.ACC_BRIDGE) != 0; + return (accFlags & AccessFlags.BRIDGE) != 0; } public boolean isVarArgs() { - return (accFlags & AccessFlags.ACC_VARARGS) != 0; + return (accFlags & AccessFlags.VARARGS) != 0; } public boolean isSynchronized() { - return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0; + return (accFlags & (AccessFlags.SYNCHRONIZED | AccessFlags.DECLARED_SYNCHRONIZED)) != 0; } public boolean isTransient() { - return (accFlags & AccessFlags.ACC_TRANSIENT) != 0; + return (accFlags & AccessFlags.TRANSIENT) != 0; } public boolean isVolatile() { - return (accFlags & AccessFlags.ACC_VOLATILE) != 0; + return (accFlags & AccessFlags.VOLATILE) != 0; } public AFType getType() { @@ -174,14 +173,14 @@ public class AccessInfo { break; case CLASS: - if ((accFlags & AccessFlags.ACC_STRICT) != 0) { + if ((accFlags & AccessFlags.STRICT) != 0) { code.append("strict "); } if (Consts.DEBUG) { - if ((accFlags & AccessFlags.ACC_SUPER) != 0) { + if ((accFlags & AccessFlags.SUPER) != 0) { code.append("/* super */ "); } - if ((accFlags & AccessFlags.ACC_ENUM) != 0) { + if ((accFlags & AccessFlags.ENUM) != 0) { code.append("/* enum */ "); } } @@ -209,25 +208,12 @@ public class AccessInfo { throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility()); } - public String rawString() { - switch (type) { - case CLASS: - return AccessFlags.classString(accFlags); - case FIELD: - return AccessFlags.fieldString(accFlags); - case METHOD: - return AccessFlags.methodString(accFlags); - default: - return "?"; - } - } - public int rawValue() { return accFlags; } @Override public String toString() { - return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')'; + return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')'; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index e58e3b33c..a2b2f6e25 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -37,13 +36,6 @@ public final class ClassInfo implements Comparable { return root.getInfoStorage().putCls(newClsInfo); } - public static ClassInfo fromDex(DexNode dex, int clsIndex) { - if (clsIndex == DexNode.NO_INDEX) { - throw new JadxRuntimeException("NO_INDEX for class"); - } - return fromType(dex.root(), dex.getType(clsIndex)); - } - public static ClassInfo fromName(RootNode root, String clsName) { return fromType(root, ArgType.object(clsName)); } 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 d71095fdd..a605c4d44 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 @@ -10,12 +10,12 @@ import org.jetbrains.annotations.Nullable; import jadx.api.JadxArgs; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.RootNode; public class ConstStorage { @@ -72,10 +72,10 @@ public class ConstStorage { if (accFlags.isStatic() && accFlags.isFinal()) { FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null - && fv.getValue() != null + && fv.getEncodedValue() != null && fv.getValueType() == FieldInitAttr.InitType.CONST && fv != FieldInitAttr.NULL_VALUE) { - addConstField(cls, f, fv.getValue(), accFlags.isPublic()); + addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic()); } } } @@ -98,9 +98,9 @@ public class ConstStorage { if (!replaceEnabled) { return null; } - DexNode dex = cls.dex(); + RootNode root = cls.root(); if (value instanceof Integer) { - FieldNode rField = getResourceField((Integer) value, dex); + FieldNode rField = getResourceField((Integer) value, root); if (rField != null) { return rField; } @@ -125,7 +125,7 @@ public class ConstStorage { if (parentClass == null) { break; } - current = dex.resolveClass(parentClass); + current = root.resolveClass(parentClass); } if (searchGlobal) { return globalValues.get(value); @@ -134,12 +134,12 @@ public class ConstStorage { } @Nullable - private FieldNode getResourceField(Integer value, DexNode dex) { + private FieldNode getResourceField(Integer value, RootNode root) { String str = resourcesNames.get(value); if (str == null) { return null; } - ClassNode appResClass = dex.root().getAppResClass(); + ClassNode appResClass = root.getAppResClass(); if (appResClass == null) { return null; } 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 ba9a2e782..8aec5c2d2 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,11 +2,10 @@ package jadx.core.dex.info; import java.util.Objects; -import com.android.dex.FieldId; - +import jadx.api.plugins.input.data.IFieldData; import jadx.core.codegen.TypeGen; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; +import jadx.core.dex.nodes.RootNode; public final class FieldInfo { @@ -22,17 +21,15 @@ public final class FieldInfo { this.alias = name; } - public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) { + public static FieldInfo from(RootNode root, ClassInfo declClass, String name, ArgType type) { FieldInfo field = new FieldInfo(declClass, name, type); - return dex.root().getInfoStorage().getField(field); + return root.getInfoStorage().getField(field); } - public static FieldInfo fromDex(DexNode dex, int index) { - FieldId field = dex.getFieldId(index); - return from(dex, - ClassInfo.fromDex(dex, field.getDeclaringClassIndex()), - dex.getString(field.getNameIndex()), - dex.getType(field.getTypeIndex())); + 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())); + return root.getInfoStorage().getField(field); } public String getName() { diff --git a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java index b0a79a5a2..9daff3731 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/InfoStorage.java @@ -4,14 +4,11 @@ import java.util.HashMap; import java.util.Map; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; -import jadx.core.utils.exceptions.JadxRuntimeException; public class InfoStorage { private final Map classes = new HashMap<>(); private final Map fields = new HashMap<>(); - private final Map methods = new HashMap<>(); // use only one MethodInfo instance private final Map uniqueMethods = new HashMap<>(); @@ -26,27 +23,6 @@ public class InfoStorage { } } - private static int generateMethodLookupId(DexNode dex, int mthId) { - return dex.getDexId() << 16 | mthId; - } - - public MethodInfo getMethod(DexNode dex, int mtdId) { - synchronized (methods) { - return methods.get(generateMethodLookupId(dex, mtdId)); - } - } - - public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo methodInfo) { - synchronized (methods) { - MethodInfo uniqueMethodInfo = putMethod(methodInfo); - MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), uniqueMethodInfo); - if (prev != null && prev != uniqueMethodInfo) { - throw new JadxRuntimeException("Method lookup id collision: " + methodInfo + ", " + prev + ", " + uniqueMethodInfo); - } - return uniqueMethodInfo; - } - } - public MethodInfo putMethod(MethodInfo newMth) { synchronized (uniqueMethods) { MethodInfo prev = uniqueMethods.get(newMth); diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index f8058cb0f..a6b24ba26 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -5,12 +5,9 @@ import java.util.Objects; import org.jetbrains.annotations.Nullable; -import com.android.dex.MethodId; -import com.android.dex.ProtoId; - +import jadx.api.plugins.input.data.IMethodData; import jadx.core.codegen.TypeGen; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.Utils; @@ -34,20 +31,13 @@ public final class MethodInfo implements Comparable { this.shortId = makeShortId(name, argTypes, retType); } - public static MethodInfo fromDex(DexNode dex, int mthIndex) { - MethodInfo storageMth = dex.root().getInfoStorage().getMethod(dex, mthIndex); - if (storageMth != null) { - return storageMth; - } - MethodId mthId = dex.getMethodId(mthIndex); - String mthName = dex.getString(mthId.getNameIndex()); - ClassInfo parentClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex()); - - ProtoId proto = dex.getProtoId(mthId.getProtoIndex()); - ArgType returnType = dex.getType(proto.getReturnTypeIndex()); - List args = dex.readParamList(proto.getParametersOffset()); - MethodInfo newMth = new MethodInfo(parentClass, mthName, args, returnType); - return dex.root().getInfoStorage().putMethod(dex, mthIndex, newMth); + public static MethodInfo fromData(RootNode root, IMethodData methodData) { + ArgType parentClsType = ArgType.parse(methodData.getParentClassType()); + ClassInfo parentClass = ClassInfo.fromType(root, parentClsType); + ArgType returnType = ArgType.parse(methodData.getReturnType()); + List args = Utils.collectionMap(methodData.getArgTypes(), ArgType::parse); + MethodInfo newMth = new MethodInfo(parentClass, methodData.getName(), args, returnType); + return root.getInfoStorage().putMethod(newMth); } public static MethodInfo fromDetails(RootNode rootNode, ClassInfo declClass, String name, List args, ArgType retType) { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java index 6346acc99..a360a6678 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/ArithNode.java @@ -1,7 +1,6 @@ package jadx.core.dex.instructions; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -14,12 +13,12 @@ public class ArithNode extends InsnNode { private final ArithOp op; - public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) { + public ArithNode(InsnData insn, ArithOp op, ArgType type, boolean literal) { super(InsnType.ARITH, 2); this.op = op; setResult(InsnArg.reg(insn, 0, type)); - int rc = insn.getRegisterCount(); + int rc = insn.getRegsCount(); if (literal) { if (rc == 1) { // self diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java similarity index 54% rename from jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java rename to jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java index 3cc11f566..f24a5066b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayData.java @@ -4,8 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction; - +import jadx.api.plugins.input.insns.custom.IArrayPayload; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; @@ -13,7 +12,7 @@ import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.InsnNode; import jadx.core.utils.exceptions.JadxRuntimeException; -public final class FillArrayNode extends InsnNode { +public final class FillArrayData extends InsnNode { private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE); private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR); @@ -22,21 +21,22 @@ public final class FillArrayNode extends InsnNode { private final Object data; private final int size; + private final int elemSize; private ArgType elemType; - public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) { - this(payload.getData(), payload.getSize(), getElementType(payload.getElementWidthUnit())); - addArg(InsnArg.reg(resReg, ArgType.array(elemType))); + public FillArrayData(IArrayPayload payload) { + this(payload.getData(), payload.getSize(), payload.getElementSize()); } - private FillArrayNode(Object data, int size, ArgType elemType) { - super(InsnType.FILL_ARRAY, 1); + private FillArrayData(Object data, int size, int elemSize) { + super(InsnType.FILL_ARRAY_DATA, 0); this.data = data; this.size = size; - this.elemType = elemType; + this.elemSize = elemSize; + this.elemType = getElementType(elemSize); } - private static ArgType getElementType(short elementWidthUnit) { + private static ArgType getElementType(int elementWidthUnit) { switch (elementWidthUnit) { case 1: return ONE_BYTE_TYPE; @@ -66,24 +66,29 @@ public final class FillArrayNode extends InsnNode { public List getLiteralArgs(ArgType type) { List list = new ArrayList<>(size); Object array = data; - if (array instanceof int[]) { - for (int b : (int[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof byte[]) { - for (byte b : (byte[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof short[]) { - for (short b : (short[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else if (array instanceof long[]) { - for (long b : (long[]) array) { - list.add(InsnArg.lit(b, type)); - } - } else { - throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type); + switch (elemSize) { + case 1: + for (byte b : (byte[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 2: + for (short b : (short[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 4: + for (int b : (int[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + case 8: + for (long b : (long[]) array) { + list.add(InsnArg.lit(b, type)); + } + break; + default: + throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type); } return list; } @@ -93,32 +98,33 @@ public final class FillArrayNode extends InsnNode { if (this == obj) { return true; } - if (!(obj instanceof FillArrayNode) || !super.isSame(obj)) { + if (!(obj instanceof FillArrayData) || !super.isSame(obj)) { return false; } - FillArrayNode other = (FillArrayNode) obj; + FillArrayData other = (FillArrayData) obj; return elemType.equals(other.elemType) && data == other.data; } @Override public InsnNode copy() { - return copyCommonParams(new FillArrayNode(data, size, elemType)); + FillArrayData copy = new FillArrayData(data, size, elemSize); + copy.elemType = this.elemType; + return copyCommonParams(copy); } public String dataToString() { - if (data instanceof int[]) { - return Arrays.toString((int[]) data); + switch (elemSize) { + case 1: + return Arrays.toString((byte[]) data); + case 2: + return Arrays.toString((short[]) data); + case 4: + return Arrays.toString((int[]) data); + case 8: + return Arrays.toString((long[]) data); + default: + return "?"; } - if (data instanceof short[]) { - return Arrays.toString((short[]) data); - } - if (data instanceof byte[]) { - return Arrays.toString((byte[]) data); - } - if (data instanceof long[]) { - return Arrays.toString((long[]) data); - } - return "?"; } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java new file mode 100644 index 000000000..b256f7e24 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/FillArrayInsn.java @@ -0,0 +1,67 @@ +package jadx.core.dex.instructions; + +import java.util.List; +import java.util.Objects; + +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.InsnNode; + +public final class FillArrayInsn extends InsnNode { + private final int target; + private FillArrayData arrayData; + + public FillArrayInsn(InsnArg arg, int target) { + super(InsnType.FILL_ARRAY, 1); + this.target = target; + addArg(arg); + } + + public int getTarget() { + return target; + } + + public void setArrayData(FillArrayData arrayData) { + this.arrayData = arrayData; + } + + @Override + public boolean isSame(InsnNode obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof FillArrayInsn) || !super.isSame(obj)) { + return false; + } + FillArrayInsn other = (FillArrayInsn) obj; + return Objects.equals(arrayData, other.arrayData); + } + + @Override + public InsnNode copy() { + FillArrayInsn copy = new FillArrayInsn(getArg(0), target); + return copyCommonParams(copy); + } + + @Override + public String toString() { + return super.toString() + ", data: " + arrayData; + } + + public int getSize() { + return arrayData.getSize(); + } + + public ArgType getElementType() { + return arrayData.getElementType(); + } + + public List getLiteralArgs(ArgType elType) { + return arrayData.getLiteralArgs(elType); + } + + public String dataToString() { + return Objects.toString(arrayData); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java index d137b913a..091b2816d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IfNode.java @@ -2,8 +2,7 @@ package jadx.core.dex.instructions; import java.util.List; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.PrimitiveType; @@ -21,12 +20,12 @@ public class IfNode extends GotoNode { private BlockNode thenBlock; private BlockNode elseBlock; - public IfNode(DecodedInstruction insn, IfOp op) { + public IfNode(InsnData insn, IfOp op) { super(InsnType.IF, insn.getTarget(), 2); this.op = op; ArgType argType = narrowTypeByOp(op); addArg(InsnArg.reg(insn, 0, argType)); - if (insn.getRegisterCount() == 1) { + if (insn.getRegsCount() == 1) { addArg(InsnArg.lit(0, argType)); } else { addArg(InsnArg.reg(insn, 1, argType)); 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 67b318a6a..d403c9cbc 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,21 +1,13 @@ package jadx.core.dex.instructions; -import java.io.EOFException; -import java.util.function.Predicate; - import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.Code; -import com.android.dx.io.OpcodeInfo; -import com.android.dx.io.Opcodes; -import com.android.dx.io.instructions.DecodedInstruction; -import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction; -import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction; -import com.android.dx.io.instructions.ShortArrayCodeInput; -import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction; - +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.custom.IArrayPayload; +import jadx.api.plugins.input.insns.custom.ISwitchPayload; import jadx.core.Consts; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; @@ -23,642 +15,497 @@ 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.instructions.args.RegisterArg; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.InsnUtils; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.DecodeException; public class InsnDecoder { private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class); private final MethodNode method; - private final DexNode dex; - private DecodedInstruction[] insnArr; + private final RootNode root; public InsnDecoder(MethodNode mthNode) { this.method = mthNode; - this.dex = method.dex(); + this.root = method.root(); } - public void decodeInsns(Code mthCode) throws DecodeException { - short[] encodedInstructions = mthCode.getInstructions(); - int size = encodedInstructions.length; - DecodedInstruction[] decoded = new DecodedInstruction[size]; - ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); - try { - while (in.hasMore()) { - decoded[in.cursor()] = decodeRawInsn(in); + public InsnNode[] process(ICodeReader codeReader) { + InsnNode[] instructions = new InsnNode[codeReader.getInsnsCount()]; + codeReader.visitInstructions(rawInsn -> { + int offset = rawInsn.getOffset(); + InsnNode insn; + try { + rawInsn.decode(); + insn = decode(rawInsn, offset); + insn.setOffset(offset); + } catch (Exception e) { + LOG.error("Failed to decode insn: " + rawInsn + ", method: " + method, e); + insn = new InsnNode(InsnType.NOP, 0); } - } catch (Exception e) { - throw new DecodeException(method, e.getMessage(), e); - } - insnArr = decoded; - } - - private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException { - int opcodeUnit = in.read(); - int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit); - OpcodeInfo.Info opcodeInfo; - try { - opcodeInfo = OpcodeInfo.get(opcode); - } catch (IllegalArgumentException e) { - LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage()); - opcodeInfo = OpcodeInfo.NOP; - } - return opcodeInfo.getFormat().decode(opcodeUnit, in); - } - - public InsnNode[] process() throws DecodeException { - InsnNode[] instructions = new InsnNode[insnArr.length]; - for (int i = 0; i < insnArr.length; i++) { - DecodedInstruction rawInsn = insnArr[i]; - if (rawInsn != null) { - InsnNode insn = decode(rawInsn, i); - insn.setOffset(i); - instructions[i] = insn; - } else { - instructions[i] = null; - } - } - insnArr = null; + instructions[offset] = insn; + }); return instructions; } @NotNull - private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException { + private InsnNode decode(InsnData insn, int offset) throws DecodeException { switch (insn.getOpcode()) { - case Opcodes.NOP: - case Opcodes.PACKED_SWITCH_PAYLOAD: - case Opcodes.SPARSE_SWITCH_PAYLOAD: - case Opcodes.FILL_ARRAY_DATA_PAYLOAD: + case NOP: return new InsnNode(InsnType.NOP, 0); // move-result will be process in invoke and filled-new-array instructions - case Opcodes.MOVE_RESULT: - case Opcodes.MOVE_RESULT_WIDE: - case Opcodes.MOVE_RESULT_OBJECT: - return new InsnNode(InsnType.NOP, 0); + case MOVE_RESULT: + return insn(InsnType.MOVE_RESULT, InsnArg.reg(insn, 0, ArgType.UNKNOWN)); - case Opcodes.CONST: - case Opcodes.CONST_4: - case Opcodes.CONST_16: - case Opcodes.CONST_HIGH16: + case CONST: LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW); return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg); - case Opcodes.CONST_WIDE: - case Opcodes.CONST_WIDE_16: - case Opcodes.CONST_WIDE_32: - case Opcodes.CONST_WIDE_HIGH16: + case CONST_WIDE: LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE); return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg); - case Opcodes.CONST_STRING: - case Opcodes.CONST_STRING_JUMBO: - InsnNode constStrInsn = new ConstStringNode(dex.getString(insn.getIndex())); + case CONST_STRING: + InsnNode constStrInsn = new ConstStringNode(insn.getIndexAsString()); constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING)); return constStrInsn; - case Opcodes.CONST_CLASS: { - ArgType clsType = dex.getType(insn.getIndex()); + case CONST_CLASS: { + ArgType clsType = ArgType.parse(insn.getIndexAsType()); InsnNode constClsInsn = new ConstClassNode(clsType); - constClsInsn.setResult( - InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType))); + constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType))); return constClsInsn; } - case Opcodes.MOVE: - case Opcodes.MOVE_16: - case Opcodes.MOVE_FROM16: + case MOVE: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.NARROW), InsnArg.reg(insn, 1, ArgType.NARROW)); - case Opcodes.MOVE_WIDE: - case Opcodes.MOVE_WIDE_16: - case Opcodes.MOVE_WIDE_FROM16: + case MOVE_WIDE: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.WIDE), InsnArg.reg(insn, 1, ArgType.WIDE)); - case Opcodes.MOVE_OBJECT: - case Opcodes.MOVE_OBJECT_16: - case Opcodes.MOVE_OBJECT_FROM16: + case MOVE_OBJECT: return insn(InsnType.MOVE, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT), InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT)); - case Opcodes.ADD_INT: - case Opcodes.ADD_INT_2ADDR: + case ADD_INT: return arith(insn, ArithOp.ADD, ArgType.INT); - case Opcodes.ADD_DOUBLE: - case Opcodes.ADD_DOUBLE_2ADDR: + case ADD_DOUBLE: return arith(insn, ArithOp.ADD, ArgType.DOUBLE); - case Opcodes.ADD_FLOAT: - case Opcodes.ADD_FLOAT_2ADDR: + case ADD_FLOAT: return arith(insn, ArithOp.ADD, ArgType.FLOAT); - case Opcodes.ADD_LONG: - case Opcodes.ADD_LONG_2ADDR: + case ADD_LONG: return arith(insn, ArithOp.ADD, ArgType.LONG); - case Opcodes.ADD_INT_LIT8: - case Opcodes.ADD_INT_LIT16: + case ADD_INT_LIT: return arithLit(insn, ArithOp.ADD, ArgType.INT); - case Opcodes.SUB_INT: - case Opcodes.SUB_INT_2ADDR: + case SUB_INT: return arith(insn, ArithOp.SUB, ArgType.INT); - case Opcodes.RSUB_INT_LIT8: - case Opcodes.RSUB_INT: // LIT16 + case RSUB_INT: return new ArithNode(ArithOp.SUB, InsnArg.reg(insn, 0, ArgType.INT), InsnArg.lit(insn, ArgType.INT), InsnArg.reg(insn, 1, ArgType.INT)); - case Opcodes.SUB_LONG: - case Opcodes.SUB_LONG_2ADDR: + case SUB_LONG: return arith(insn, ArithOp.SUB, ArgType.LONG); - case Opcodes.SUB_FLOAT: - case Opcodes.SUB_FLOAT_2ADDR: + case SUB_FLOAT: return arith(insn, ArithOp.SUB, ArgType.FLOAT); - case Opcodes.SUB_DOUBLE: - case Opcodes.SUB_DOUBLE_2ADDR: + case SUB_DOUBLE: return arith(insn, ArithOp.SUB, ArgType.DOUBLE); - case Opcodes.MUL_INT: - case Opcodes.MUL_INT_2ADDR: + case MUL_INT: return arith(insn, ArithOp.MUL, ArgType.INT); - case Opcodes.MUL_DOUBLE: - case Opcodes.MUL_DOUBLE_2ADDR: + case MUL_DOUBLE: return arith(insn, ArithOp.MUL, ArgType.DOUBLE); - case Opcodes.MUL_FLOAT: - case Opcodes.MUL_FLOAT_2ADDR: + case MUL_FLOAT: return arith(insn, ArithOp.MUL, ArgType.FLOAT); - case Opcodes.MUL_LONG: - case Opcodes.MUL_LONG_2ADDR: + case MUL_LONG: return arith(insn, ArithOp.MUL, ArgType.LONG); - case Opcodes.MUL_INT_LIT8: - case Opcodes.MUL_INT_LIT16: + case MUL_INT_LIT: return arithLit(insn, ArithOp.MUL, ArgType.INT); - case Opcodes.DIV_INT: - case Opcodes.DIV_INT_2ADDR: + case DIV_INT: return arith(insn, ArithOp.DIV, ArgType.INT); - case Opcodes.REM_INT: - case Opcodes.REM_INT_2ADDR: + case REM_INT: return arith(insn, ArithOp.REM, ArgType.INT); - case Opcodes.REM_LONG: - case Opcodes.REM_LONG_2ADDR: + case REM_LONG: return arith(insn, ArithOp.REM, ArgType.LONG); - case Opcodes.REM_FLOAT: - case Opcodes.REM_FLOAT_2ADDR: + case REM_FLOAT: return arith(insn, ArithOp.REM, ArgType.FLOAT); - case Opcodes.REM_DOUBLE: - case Opcodes.REM_DOUBLE_2ADDR: + case REM_DOUBLE: return arith(insn, ArithOp.REM, ArgType.DOUBLE); - case Opcodes.DIV_DOUBLE: - case Opcodes.DIV_DOUBLE_2ADDR: + case DIV_DOUBLE: return arith(insn, ArithOp.DIV, ArgType.DOUBLE); - case Opcodes.DIV_FLOAT: - case Opcodes.DIV_FLOAT_2ADDR: + case DIV_FLOAT: return arith(insn, ArithOp.DIV, ArgType.FLOAT); - case Opcodes.DIV_LONG: - case Opcodes.DIV_LONG_2ADDR: + case DIV_LONG: return arith(insn, ArithOp.DIV, ArgType.LONG); - case Opcodes.DIV_INT_LIT8: - case Opcodes.DIV_INT_LIT16: + case DIV_INT_LIT: return arithLit(insn, ArithOp.DIV, ArgType.INT); - case Opcodes.REM_INT_LIT8: - case Opcodes.REM_INT_LIT16: + case REM_INT_LIT: return arithLit(insn, ArithOp.REM, ArgType.INT); - case Opcodes.AND_INT: - case Opcodes.AND_INT_2ADDR: + case AND_INT: return arith(insn, ArithOp.AND, ArgType.INT); - case Opcodes.AND_INT_LIT8: - case Opcodes.AND_INT_LIT16: + case AND_INT_LIT: return arithLit(insn, ArithOp.AND, ArgType.INT); - case Opcodes.XOR_INT_LIT8: - case Opcodes.XOR_INT_LIT16: + case XOR_INT_LIT: return arithLit(insn, ArithOp.XOR, ArgType.INT); - case Opcodes.AND_LONG: - case Opcodes.AND_LONG_2ADDR: + case AND_LONG: return arith(insn, ArithOp.AND, ArgType.LONG); - case Opcodes.OR_INT: - case Opcodes.OR_INT_2ADDR: + case OR_INT: return arith(insn, ArithOp.OR, ArgType.INT); - case Opcodes.OR_INT_LIT8: - case Opcodes.OR_INT_LIT16: + case OR_INT_LIT: return arithLit(insn, ArithOp.OR, ArgType.INT); - case Opcodes.XOR_INT: - case Opcodes.XOR_INT_2ADDR: + case XOR_INT: return arith(insn, ArithOp.XOR, ArgType.INT); - case Opcodes.OR_LONG: - case Opcodes.OR_LONG_2ADDR: + case OR_LONG: return arith(insn, ArithOp.OR, ArgType.LONG); - case Opcodes.XOR_LONG: - case Opcodes.XOR_LONG_2ADDR: + case XOR_LONG: return arith(insn, ArithOp.XOR, ArgType.LONG); - case Opcodes.USHR_INT: - case Opcodes.USHR_INT_2ADDR: + case USHR_INT: return arith(insn, ArithOp.USHR, ArgType.INT); - case Opcodes.USHR_LONG: - case Opcodes.USHR_LONG_2ADDR: + case USHR_LONG: return arith(insn, ArithOp.USHR, ArgType.LONG); - case Opcodes.SHL_INT: - case Opcodes.SHL_INT_2ADDR: + case SHL_INT: return arith(insn, ArithOp.SHL, ArgType.INT); - case Opcodes.SHL_LONG: - case Opcodes.SHL_LONG_2ADDR: + case SHL_LONG: return arith(insn, ArithOp.SHL, ArgType.LONG); - case Opcodes.SHR_INT: - case Opcodes.SHR_INT_2ADDR: + case SHR_INT: return arith(insn, ArithOp.SHR, ArgType.INT); - case Opcodes.SHR_LONG: - case Opcodes.SHR_LONG_2ADDR: + case SHR_LONG: return arith(insn, ArithOp.SHR, ArgType.LONG); - case Opcodes.SHL_INT_LIT8: + case SHL_INT_LIT: return arithLit(insn, ArithOp.SHL, ArgType.INT); - case Opcodes.SHR_INT_LIT8: + case SHR_INT_LIT: return arithLit(insn, ArithOp.SHR, ArgType.INT); - case Opcodes.USHR_INT_LIT8: + case USHR_INT_LIT: return arithLit(insn, ArithOp.USHR, ArgType.INT); - case Opcodes.NEG_INT: + case NEG_INT: return neg(insn, ArgType.INT); - case Opcodes.NEG_LONG: + case NEG_LONG: return neg(insn, ArgType.LONG); - case Opcodes.NEG_FLOAT: + case NEG_FLOAT: return neg(insn, ArgType.FLOAT); - case Opcodes.NEG_DOUBLE: + case NEG_DOUBLE: return neg(insn, ArgType.DOUBLE); - case Opcodes.NOT_INT: + case NOT_INT: return not(insn, ArgType.INT); - case Opcodes.NOT_LONG: + case NOT_LONG: return not(insn, ArgType.LONG); - case Opcodes.INT_TO_BYTE: + case INT_TO_BYTE: return cast(insn, ArgType.INT, ArgType.BYTE); - case Opcodes.INT_TO_CHAR: + case INT_TO_CHAR: return cast(insn, ArgType.INT, ArgType.CHAR); - case Opcodes.INT_TO_SHORT: + case INT_TO_SHORT: return cast(insn, ArgType.INT, ArgType.SHORT); - case Opcodes.INT_TO_FLOAT: + case INT_TO_FLOAT: return cast(insn, ArgType.INT, ArgType.FLOAT); - case Opcodes.INT_TO_DOUBLE: + case INT_TO_DOUBLE: return cast(insn, ArgType.INT, ArgType.DOUBLE); - case Opcodes.INT_TO_LONG: + case INT_TO_LONG: return cast(insn, ArgType.INT, ArgType.LONG); - case Opcodes.FLOAT_TO_INT: + case FLOAT_TO_INT: return cast(insn, ArgType.FLOAT, ArgType.INT); - case Opcodes.FLOAT_TO_DOUBLE: + case FLOAT_TO_DOUBLE: return cast(insn, ArgType.FLOAT, ArgType.DOUBLE); - case Opcodes.FLOAT_TO_LONG: + case FLOAT_TO_LONG: return cast(insn, ArgType.FLOAT, ArgType.LONG); - case Opcodes.DOUBLE_TO_INT: + case DOUBLE_TO_INT: return cast(insn, ArgType.DOUBLE, ArgType.INT); - case Opcodes.DOUBLE_TO_FLOAT: + case DOUBLE_TO_FLOAT: return cast(insn, ArgType.DOUBLE, ArgType.FLOAT); - case Opcodes.DOUBLE_TO_LONG: + case DOUBLE_TO_LONG: return cast(insn, ArgType.DOUBLE, ArgType.LONG); - case Opcodes.LONG_TO_INT: + case LONG_TO_INT: return cast(insn, ArgType.LONG, ArgType.INT); - case Opcodes.LONG_TO_FLOAT: + case LONG_TO_FLOAT: return cast(insn, ArgType.LONG, ArgType.FLOAT); - case Opcodes.LONG_TO_DOUBLE: + case LONG_TO_DOUBLE: return cast(insn, ArgType.LONG, ArgType.DOUBLE); - case Opcodes.IF_EQ: - case Opcodes.IF_EQZ: + case IF_EQ: + case IF_EQZ: return new IfNode(insn, IfOp.EQ); - case Opcodes.IF_NE: - case Opcodes.IF_NEZ: + case IF_NE: + case IF_NEZ: return new IfNode(insn, IfOp.NE); - case Opcodes.IF_GT: - case Opcodes.IF_GTZ: + case IF_GT: + case IF_GTZ: return new IfNode(insn, IfOp.GT); - case Opcodes.IF_GE: - case Opcodes.IF_GEZ: + case IF_GE: + case IF_GEZ: return new IfNode(insn, IfOp.GE); - case Opcodes.IF_LT: - case Opcodes.IF_LTZ: + case IF_LT: + case IF_LTZ: return new IfNode(insn, IfOp.LT); - case Opcodes.IF_LE: - case Opcodes.IF_LEZ: + case IF_LE: + case IF_LEZ: return new IfNode(insn, IfOp.LE); - case Opcodes.CMP_LONG: + case CMP_LONG: return cmp(insn, InsnType.CMP_L, ArgType.LONG); - case Opcodes.CMPL_FLOAT: + case CMPL_FLOAT: return cmp(insn, InsnType.CMP_L, ArgType.FLOAT); - case Opcodes.CMPL_DOUBLE: + case CMPL_DOUBLE: return cmp(insn, InsnType.CMP_L, ArgType.DOUBLE); - case Opcodes.CMPG_FLOAT: + case CMPG_FLOAT: return cmp(insn, InsnType.CMP_G, ArgType.FLOAT); - case Opcodes.CMPG_DOUBLE: + case CMPG_DOUBLE: return cmp(insn, InsnType.CMP_G, ArgType.DOUBLE); - case Opcodes.GOTO: - case Opcodes.GOTO_16: - case Opcodes.GOTO_32: + case GOTO: return new GotoNode(insn.getTarget()); - case Opcodes.THROW: + case THROW: return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE)); - case Opcodes.MOVE_EXCEPTION: + case MOVE_EXCEPTION: return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY)); - case Opcodes.RETURN_VOID: + case RETURN_VOID: return new InsnNode(InsnType.RETURN, 0); - case Opcodes.RETURN: - case Opcodes.RETURN_WIDE: - case Opcodes.RETURN_OBJECT: + case RETURN: return insn(InsnType.RETURN, null, InsnArg.reg(insn, 0, method.getReturnType())); - case Opcodes.INSTANCE_OF: - InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1); + case INSTANCE_OF: + InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, ArgType.parse(insn.getIndexAsType()), 1); instInsn.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN)); instInsn.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT)); return instInsn; - case Opcodes.CHECK_CAST: - ArgType castType = dex.getType(insn.getIndex()); + case CHECK_CAST: + 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)); return checkCastInsn; - case Opcodes.IGET: - case Opcodes.IGET_BOOLEAN: - case Opcodes.IGET_BYTE: - case Opcodes.IGET_CHAR: - case Opcodes.IGET_SHORT: - case Opcodes.IGET_WIDE: - case Opcodes.IGET_OBJECT: - FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex()); + case IGET: + FieldInfo igetFld = FieldInfo.fromData(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 Opcodes.IPUT: - case Opcodes.IPUT_BOOLEAN: - case Opcodes.IPUT_BYTE: - case Opcodes.IPUT_CHAR: - case Opcodes.IPUT_SHORT: - case Opcodes.IPUT_WIDE: - case Opcodes.IPUT_OBJECT: - FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex()); + case IPUT: + FieldInfo iputFld = FieldInfo.fromData(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 Opcodes.SGET: - case Opcodes.SGET_BOOLEAN: - case Opcodes.SGET_BYTE: - case Opcodes.SGET_CHAR: - case Opcodes.SGET_SHORT: - case Opcodes.SGET_WIDE: - case Opcodes.SGET_OBJECT: - FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex()); + case SGET: + FieldInfo sgetFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0); sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld))); return sgetInsn; - case Opcodes.SPUT: - case Opcodes.SPUT_BOOLEAN: - case Opcodes.SPUT_BYTE: - case Opcodes.SPUT_CHAR: - case Opcodes.SPUT_SHORT: - case Opcodes.SPUT_WIDE: - case Opcodes.SPUT_OBJECT: - FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex()); + case SPUT: + FieldInfo sputFld = FieldInfo.fromData(root, insn.getIndexAsField()); InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1); sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld))); return sputInsn; - case Opcodes.ARRAY_LENGTH: + case ARRAY_LENGTH: InsnNode arrLenInsn = new InsnNode(InsnType.ARRAY_LENGTH, 1); arrLenInsn.setResult(InsnArg.reg(insn, 0, ArgType.INT)); arrLenInsn.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN))); return arrLenInsn; - case Opcodes.AGET: + case AGET: return arrayGet(insn, ArgType.INT_FLOAT); - case Opcodes.AGET_BOOLEAN: + case AGET_BOOLEAN: return arrayGet(insn, ArgType.BOOLEAN); - case Opcodes.AGET_BYTE: + case AGET_BYTE: return arrayGet(insn, ArgType.BYTE); - case Opcodes.AGET_CHAR: + case AGET_CHAR: return arrayGet(insn, ArgType.CHAR); - case Opcodes.AGET_SHORT: + case AGET_SHORT: return arrayGet(insn, ArgType.SHORT); - case Opcodes.AGET_WIDE: + case AGET_WIDE: return arrayGet(insn, ArgType.WIDE); - case Opcodes.AGET_OBJECT: + case AGET_OBJECT: return arrayGet(insn, ArgType.UNKNOWN_OBJECT); - case Opcodes.APUT: + case APUT: return arrayPut(insn, ArgType.INT_FLOAT); - case Opcodes.APUT_BOOLEAN: + case APUT_BOOLEAN: return arrayPut(insn, ArgType.BOOLEAN); - case Opcodes.APUT_BYTE: + case APUT_BYTE: return arrayPut(insn, ArgType.BYTE); - case Opcodes.APUT_CHAR: + case APUT_CHAR: return arrayPut(insn, ArgType.CHAR); - case Opcodes.APUT_SHORT: + case APUT_SHORT: return arrayPut(insn, ArgType.SHORT); - case Opcodes.APUT_WIDE: + case APUT_WIDE: return arrayPut(insn, ArgType.WIDE); - case Opcodes.APUT_OBJECT: + case APUT_OBJECT: return arrayPut(insn, ArgType.UNKNOWN_OBJECT); - case Opcodes.INVOKE_STATIC: + case INVOKE_STATIC: return invoke(insn, offset, InvokeType.STATIC, false); - case Opcodes.INVOKE_STATIC_RANGE: + case INVOKE_STATIC_RANGE: return invoke(insn, offset, InvokeType.STATIC, true); - case Opcodes.INVOKE_DIRECT: + case INVOKE_DIRECT: return invoke(insn, offset, InvokeType.DIRECT, false); - case Opcodes.INVOKE_INTERFACE: + case INVOKE_INTERFACE: return invoke(insn, offset, InvokeType.INTERFACE, false); - case Opcodes.INVOKE_SUPER: + case INVOKE_SUPER: return invoke(insn, offset, InvokeType.SUPER, false); - case Opcodes.INVOKE_VIRTUAL: + case INVOKE_VIRTUAL: return invoke(insn, offset, InvokeType.VIRTUAL, false); - case Opcodes.INVOKE_DIRECT_RANGE: + case INVOKE_DIRECT_RANGE: return invoke(insn, offset, InvokeType.DIRECT, true); - case Opcodes.INVOKE_INTERFACE_RANGE: + case INVOKE_INTERFACE_RANGE: return invoke(insn, offset, InvokeType.INTERFACE, true); - case Opcodes.INVOKE_SUPER_RANGE: + case INVOKE_SUPER_RANGE: return invoke(insn, offset, InvokeType.SUPER, true); - case Opcodes.INVOKE_VIRTUAL_RANGE: + case INVOKE_VIRTUAL_RANGE: return invoke(insn, offset, InvokeType.VIRTUAL, true); - case Opcodes.NEW_INSTANCE: - ArgType clsType = dex.getType(insn.getIndex()); + case NEW_INSTANCE: + ArgType clsType = ArgType.parse(insn.getIndexAsType()); IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0); newInstInsn.setResult(InsnArg.reg(insn, 0, clsType)); return newInstInsn; - case Opcodes.NEW_ARRAY: - ArgType arrType = dex.getType(insn.getIndex()); + case NEW_ARRAY: + ArgType arrType = ArgType.parse(insn.getIndexAsType()); return new NewArrayNode(arrType, InsnArg.reg(insn, 0, arrType), InsnArg.typeImmutableReg(insn, 1, ArgType.INT)); - case Opcodes.FILL_ARRAY_DATA: - return fillArray(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())); - case Opcodes.FILLED_NEW_ARRAY: - return filledNewArray(insn, offset, false); - case Opcodes.FILLED_NEW_ARRAY_RANGE: - return filledNewArray(insn, offset, true); + case FILLED_NEW_ARRAY: + return filledNewArray(insn, false); + case FILLED_NEW_ARRAY_RANGE: + return filledNewArray(insn, true); - case Opcodes.PACKED_SWITCH: - return decodeSwitch(insn, offset, true); + case PACKED_SWITCH: + return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), true); + case SPARSE_SWITCH: + return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), false); - case Opcodes.SPARSE_SWITCH: - return decodeSwitch(insn, offset, false); + case PACKED_SWITCH_PAYLOAD: + case SPARSE_SWITCH_PAYLOAD: + return new SwitchData(((ISwitchPayload) insn.getPayload())); - case Opcodes.MONITOR_ENTER: + case MONITOR_ENTER: return insn(InsnType.MONITOR_ENTER, null, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT)); - case Opcodes.MONITOR_EXIT: + case MONITOR_EXIT: return insn(InsnType.MONITOR_EXIT, null, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT)); default: - throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + '\''); + throw new DecodeException("Unknown instruction: '" + insn + '\''); } } private ArgType tryResolveFieldType(FieldInfo igetFld) { - FieldNode fieldNode = dex.resolveField(igetFld); + FieldNode fieldNode = root.resolveField(igetFld); if (fieldNode != null) { return fieldNode.getType(); } return igetFld.getType(); } - private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) { - int payloadOffset = insn.getTarget(); - DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset); - Object[] keys; - int[] targets; - if (packed) { - PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload; - targets = ps.getTargets(); - keys = new Object[targets.length]; - int k = ps.getFirstKey(); - for (int i = 0; i < keys.length; i++) { - keys[i] = k++; - } - } else { - SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload; - targets = ss.getTargets(); - keys = new Object[targets.length]; - for (int i = 0; i < keys.length; i++) { - keys[i] = ss.getKeys()[i]; - } - } - // convert from relative to absolute offsets - for (int i = 0; i < targets.length; i++) { - targets[i] = targets[i] - payloadOffset + offset; - } - int nextOffset = getNextInsnOffset(insnArr, offset); - return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset, packed); - } - - private InsnNode fillArray(DecodedInstruction insn) { - DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget()); - return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload); - } - - private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) { - int resReg = getMoveResultRegister(insnArr, offset); - ArgType arrType = dex.getType(insn.getIndex()); + private InsnNode filledNewArray(InsnData insn, boolean isRange) { + ArgType arrType = ArgType.parse(insn.getIndexAsType()); ArgType elType = arrType.getArrayElement(); boolean typeImmutable = elType.isPrimitive(); - int regsCount = insn.getRegisterCount(); + int regsCount = insn.getRegsCount(); InsnArg[] regs = new InsnArg[regsCount]; if (isRange) { - int r = insn.getA(); + int r = insn.getReg(0); for (int i = 0; i < regsCount; i++) { regs[i] = InsnArg.reg(r, elType, typeImmutable); r++; } } else { for (int i = 0; i < regsCount; i++) { - int regNum = InsnUtils.getArg(insn, i); + int regNum = insn.getReg(i); regs[i] = InsnArg.reg(regNum, elType, typeImmutable); } } InsnNode node = new FilledNewArrayNode(elType, regs.length); - node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType)); + // node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType)); for (InsnArg arg : regs) { node.addArg(arg); } return node; } - private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) { + private InsnNode cmp(InsnData insn, InsnType itype, ArgType argType) { InsnNode inode = new InsnNode(itype, 2); inode.setResult(InsnArg.reg(insn, 0, ArgType.INT)); inode.addArg(InsnArg.reg(insn, 1, argType)); @@ -666,20 +513,19 @@ public class InsnDecoder { return inode; } - private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) { + private InsnNode cast(InsnData insn, ArgType from, ArgType to) { InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1); inode.setResult(InsnArg.reg(insn, 0, to)); inode.addArg(InsnArg.reg(insn, 1, from)); return inode; } - private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) { - int resReg = getMoveResultRegister(insnArr, offset); - MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex()); - return new InvokeNode(mth, insn, type, isRange, resReg); + private InsnNode invoke(InsnData insn, int offset, InvokeType type, boolean isRange) { + MethodInfo mth = MethodInfo.fromData(root, insn.getIndexAsMethod()); + return new InvokeNode(mth, insn, type, isRange); } - private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) { + private InsnNode arrayGet(InsnData insn, ArgType argType) { InsnNode inode = new InsnNode(InsnType.AGET, 2); inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType)); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); @@ -687,7 +533,7 @@ public class InsnDecoder { return inode; } - private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) { + private InsnNode arrayPut(InsnData insn, ArgType argType) { InsnNode inode = new InsnNode(InsnType.APUT, 3); inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType))); inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL)); @@ -695,11 +541,11 @@ public class InsnDecoder { return inode; } - private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) { + private InsnNode arith(InsnData insn, ArithOp op, ArgType type) { return new ArithNode(insn, op, fixTypeForBitOps(op, type), false); } - private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) { + private InsnNode arithLit(InsnData insn, ArithOp op, ArgType type) { return new ArithNode(insn, op, fixTypeForBitOps(op, type), true); } @@ -711,14 +557,14 @@ public class InsnDecoder { return type; } - private InsnNode neg(DecodedInstruction insn, ArgType type) { + private InsnNode neg(InsnData insn, ArgType type) { InsnNode inode = new InsnNode(InsnType.NEG, 1); inode.setResult(InsnArg.reg(insn, 0, type)); inode.addArg(InsnArg.reg(insn, 1, type)); return inode; } - private InsnNode not(DecodedInstruction insn, ArgType type) { + private InsnNode not(InsnData insn, ArgType type) { InsnNode inode = new InsnNode(InsnType.NOT, 1); inode.setResult(InsnArg.reg(insn, 0, type)); inode.addArg(InsnArg.reg(insn, 1, type)); @@ -737,54 +583,4 @@ public class InsnDecoder { node.addArg(arg); return node; } - - private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) { - int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset); - if (nextOffset >= 0) { - DecodedInstruction next = insnArr[nextOffset]; - int opc = next.getOpcode(); - if (opc == Opcodes.MOVE_RESULT - || opc == Opcodes.MOVE_RESULT_WIDE - || opc == Opcodes.MOVE_RESULT_OBJECT) { - return next.getA(); - } - } - return -1; - } - - private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { - DecodedInstruction payload = insnArr[offset]; - if (payload.getOpcode() == Opcodes.NOP) { - return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)]; - } - return payload; - } - - public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, null); - } - - public static int getNextInsnOffsetSkipNop(DecodedInstruction[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, i -> i.getOpcode() == Opcodes.NOP); - } - - public static int getNextInsnOffset(InsnNode[] insnArr, int offset) { - return getNextInsnOffset(insnArr, offset, null); - } - - public static int getNextInsnOffset(T[] insnArr, int offset, Predicate skip) { - int i = offset + 1; - while (i < insnArr.length) { - T insn = insnArr[i]; - if (insn == null || (skip != null && skip.test(insn))) { - i++; - } else { - break; - } - } - if (i >= insnArr.length) { - return -1; - } - return i; - } } 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 eb184a591..448ba16d8 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 @@ -23,6 +23,7 @@ public enum InsnType { CMP_G, IF, SWITCH, + SWITCH_DATA, MONITOR_ENTER, MONITOR_EXIT, @@ -32,6 +33,7 @@ public enum InsnType { ARRAY_LENGTH, FILL_ARRAY, + FILL_ARRAY_DATA, FILLED_NEW_ARRAY, AGET, @@ -47,6 +49,7 @@ public enum InsnType { SPUT, INVOKE, + MOVE_RESULT, // *** Additional instructions *** @@ -67,8 +70,5 @@ public enum InsnType { PHI, // fake insn to keep arguments which will be used in regions codegen - REGION_ARG, - - // TODO: now multidimensional arrays created using Array.newInstance function - NEW_MULTIDIM_ARRAY + REGION_ARG } 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 9b850c919..75b2f2aa1 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 @@ -2,37 +2,31 @@ package jadx.core.dex.instructions; import org.jetbrains.annotations.Nullable; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.InsnNode; -import jadx.core.utils.InsnUtils; public final class InvokeNode extends BaseInvokeNode { private final InvokeType type; private final MethodInfo mth; - public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) { + public InvokeNode(MethodInfo mth, InsnData insn, InvokeType type, boolean isRange) { super(InsnType.INVOKE, mth.getArgsCount() + (type == InvokeType.STATIC ? 0 : 1)); this.mth = mth; this.type = type; - if (resReg >= 0) { - setResult(InsnArg.reg(resReg, mth.getReturnType())); - } - - int k = isRange ? insn.getA() : 0; + int k = isRange ? insn.getReg(0) : 0; if (type != InvokeType.STATIC) { - int r = isRange ? k : InsnUtils.getArg(insn, k); + int r = isRange ? k : insn.getReg(k); addReg(r, mth.getDeclClass().getType()); k++; } for (ArgType arg : mth.getArgumentsTypes()) { - addReg(isRange ? k : InsnUtils.getArg(insn, k), arg); + addReg(isRange ? k : insn.getReg(k), arg); k += arg.getRegCount(); } } 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 new file mode 100644 index 000000000..fab77a831 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchData.java @@ -0,0 +1,41 @@ +package jadx.core.dex.instructions; + +import jadx.api.plugins.input.insns.custom.ISwitchPayload; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.InsnUtils; + +public class SwitchData extends InsnNode { + private final int size; + private final int[] keys; + private final int[] targets; + + public SwitchData(ISwitchPayload payload) { + super(InsnType.SWITCH_DATA, 0); + this.size = payload.getSize(); + this.keys = payload.getKeys(); + this.targets = payload.getTargets(); + } + + public int getSize() { + return size; + } + + public int[] getKeys() { + return keys; + } + + public int[] getTargets() { + return targets; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("switch-data {"); + for (int i = 0; i < size; i++) { + sb.append(keys[i]).append("->").append(InsnUtils.formatOffset(targets[i])).append(", "); + } + sb.append('}'); + return sb.toString(); + } +} 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 new file mode 100644 index 000000000..86db555ef --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchInsn.java @@ -0,0 +1,193 @@ +package jadx.core.dex.instructions; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import jadx.core.codegen.CodeWriter; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.InsnUtils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import static jadx.core.utils.BlockUtils.getBlockByOffset; + +public class SwitchInsn extends TargetInsnNode { + private final int dataTarget; + private final boolean packed; // type of switch insn, if true can contain filler keys + @Nullable + private SwitchData switchData; + + private int def; // next instruction + + private Object[] modifiedKeys; + private BlockNode[] targetBlocks; + private BlockNode defTargetBlock; + + public SwitchInsn(InsnArg arg, int dataTarget, boolean packed) { + super(InsnType.SWITCH, 1); + addArg(arg); + this.dataTarget = dataTarget; + this.packed = packed; + } + + 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 + public void initBlocks(BlockNode curBlock) { + if (switchData == null) { + throw new JadxRuntimeException("Switch data not yet attached"); + } + List successors = curBlock.getSuccessors(); + int[] targets = switchData.getTargets(); + int len = targets.length; + targetBlocks = new BlockNode[len]; + for (int i = 0; i < len; i++) { + targetBlocks[i] = getBlockByOffset(targets[i], successors); + } + defTargetBlock = getBlockByOffset(def, successors); + } + + @Override + public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { + if (targetBlocks == null) { + return false; + } + int count = 0; + int len = targetBlocks.length; + for (int i = 0; i < len; i++) { + if (targetBlocks[i] == origin) { + targetBlocks[i] = replace; + count++; + } + } + if (defTargetBlock == origin) { + defTargetBlock = replace; + count++; + } + return count > 0; + } + + @Override + public boolean isSame(InsnNode obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SwitchInsn) || !super.isSame(obj)) { + return false; + } + SwitchInsn other = (SwitchInsn) obj; + return dataTarget == other.dataTarget + && packed == other.packed; + } + + @Override + public InsnNode copy() { + SwitchInsn copy = new SwitchInsn(getArg(0), dataTarget, packed); + copy.switchData = switchData; + copy.def = def; + copy.targetBlocks = targetBlocks; + copy.defTargetBlock = defTargetBlock; + return copyCommonParams(copy); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + if (switchData == null) { + sb.append("no payload"); + } else { + int size = switchData.getSize(); + int[] keys = switchData.getKeys(); + if (targetBlocks != null) { + for (int i = 0; i < size; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]); + } + if (def != -1) { + sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock); + } + } else { + int[] targets = switchData.getTargets(); + for (int i = 0; i < size; i++) { + sb.append(CodeWriter.NL); + sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i])); + } + if (def != -1) { + sb.append(CodeWriter.NL); + sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); + } + } + } + return sb.toString(); + } + + public int getDataTarget() { + return dataTarget; + } + + public boolean isPacked() { + return packed; + } + + public int getDefaultCaseOffset() { + return def; + } + + @NotNull + private SwitchData getSwitchData() { + if (switchData == null) { + throw new JadxRuntimeException("Switch data not yet attached"); + } + return switchData; + } + + public int[] getTargets() { + return getSwitchData().getTargets(); + } + + public int[] getKeys() { + return getSwitchData().getKeys(); + } + + public Object getKey(int i) { + if (modifiedKeys != null) { + return modifiedKeys[i]; + } + return getSwitchData().getKeys()[i]; + } + + public void modifyKey(int i, Object newKey) { + if (modifiedKeys == null) { + int[] keys = getKeys(); + int caseCount = keys.length; + Object[] newKeys = new Object[caseCount]; + for (int j = 0; j < caseCount; j++) { + newKeys[j] = keys[j]; + } + modifiedKeys = newKeys; + } + modifiedKeys[i] = newKey; + } + + public BlockNode[] getTargetBlocks() { + return targetBlocks; + } + + public BlockNode getDefTargetBlock() { + return defTargetBlock; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java deleted file mode 100644 index 374ddd412..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/SwitchNode.java +++ /dev/null @@ -1,142 +0,0 @@ -package jadx.core.dex.instructions; - -import java.util.Arrays; -import java.util.List; - -import jadx.core.codegen.CodeWriter; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.utils.InsnUtils; - -import static jadx.core.utils.BlockUtils.getBlockByOffset; - -public class SwitchNode extends TargetInsnNode { - - private final Object[] keys; - private final int[] targets; - private final int def; // next instruction - private final boolean packed; // type of switch insn, if true can contain filler keys - - private BlockNode[] targetBlocks; - private BlockNode defTargetBlock; - - public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def, boolean packed) { - this(keys, targets, def, packed); - addArg(arg); - } - - private SwitchNode(Object[] keys, int[] targets, int def, boolean packed) { - super(InsnType.SWITCH, 1); - this.keys = keys; - this.targets = targets; - this.def = def; - this.packed = packed; - } - - public int getCasesCount() { - return keys.length; - } - - public Object[] getKeys() { - return keys; - } - - public int[] getTargets() { - return targets; - } - - public int getDefaultCaseOffset() { - return def; - } - - public boolean isPacked() { - return packed; - } - - public BlockNode[] getTargetBlocks() { - return targetBlocks; - } - - public BlockNode getDefTargetBlock() { - return defTargetBlock; - } - - @Override - public void initBlocks(BlockNode curBlock) { - List successors = curBlock.getSuccessors(); - int len = targets.length; - targetBlocks = new BlockNode[len]; - for (int i = 0; i < len; i++) { - targetBlocks[i] = getBlockByOffset(targets[i], successors); - } - defTargetBlock = getBlockByOffset(def, successors); - } - - @Override - public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) { - if (targetBlocks == null) { - return false; - } - int count = 0; - int len = targetBlocks.length; - for (int i = 0; i < len; i++) { - if (targetBlocks[i] == origin) { - targetBlocks[i] = replace; - count++; - } - } - if (defTargetBlock == origin) { - defTargetBlock = replace; - count++; - } - return count > 0; - } - - @Override - public boolean isSame(InsnNode obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof SwitchNode) || !super.isSame(obj)) { - return false; - } - SwitchNode other = (SwitchNode) obj; - return def == other.def - && Arrays.equals(keys, other.keys) - && Arrays.equals(targets, other.targets); - } - - @Override - public InsnNode copy() { - SwitchNode copy = new SwitchNode(keys, targets, def, packed); - copy.targetBlocks = targetBlocks; - copy.defTargetBlock = defTargetBlock; - return copyCommonParams(copy); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString()); - if (targetBlocks == null) { - for (int i = 0; i < keys.length; i++) { - sb.append(CodeWriter.NL); - sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i])); - } - if (def != -1) { - sb.append(CodeWriter.NL); - sb.append(" default: goto ").append(InsnUtils.formatOffset(def)); - } - } else { - for (int i = 0; i < keys.length; i++) { - sb.append(CodeWriter.NL); - sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]); - } - if (def != -1) { - sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock); - } - } - return sb.toString(); - } -} 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 2ffec4f7a..9237ea5cb 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 @@ -10,7 +10,6 @@ import org.jetbrains.annotations.NotNull; import jadx.core.Consts; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; import jadx.core.utils.Utils; @@ -83,6 +82,8 @@ public abstract class ArgType { return STRING; case Consts.CLASS_CLASS: return CLASS; + case Consts.CLASS_THROWABLE: + return THROWABLE; default: return new ObjectType(cleanObjectName); } @@ -739,12 +740,12 @@ public abstract class ArgType { return false; } - public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) { + public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) { if (!type.isObject() || type.isGenericType()) { return type; } - ClassNode cls = dex.resolveClass(type); + ClassNode cls = root.resolveClass(type); if (cls == null) { return type; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index 207452a82..2c563698c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -5,14 +5,12 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.InsnRemover; -import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxRuntimeException; /** @@ -30,19 +28,19 @@ public abstract class InsnArg extends Typed { return new RegisterArg(regNum, type); } - public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) { - return reg(InsnUtils.getArg(insn, argNum), type); + public static RegisterArg reg(InsnData insn, int argNum, ArgType type) { + return reg(insn.getReg(argNum), type); } - public static RegisterArg typeImmutableIfKnownReg(DecodedInstruction insn, int argNum, ArgType type) { + public static RegisterArg typeImmutableIfKnownReg(InsnData insn, int argNum, ArgType type) { if (type.isTypeKnown()) { - return typeImmutableReg(InsnUtils.getArg(insn, argNum), type); + return typeImmutableReg(insn.getReg(argNum), type); } - return reg(InsnUtils.getArg(insn, argNum), type); + return reg(insn.getReg(argNum), type); } - public static RegisterArg typeImmutableReg(DecodedInstruction insn, int argNum, ArgType type) { - return typeImmutableReg(InsnUtils.getArg(insn, argNum), type); + public static RegisterArg typeImmutableReg(InsnData insn, int argNum, ArgType type) { + return typeImmutableReg(insn.getReg(argNum), type); } public static RegisterArg typeImmutableReg(int regNum, ArgType type) { @@ -61,7 +59,7 @@ public abstract class InsnArg extends Typed { return new LiteralArg(literal, type); } - public static LiteralArg lit(DecodedInstruction insn, ArgType type) { + public static LiteralArg lit(InsnData insn, ArgType type) { return lit(insn.getLiteral(), type); } 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 30629806b..ca1516337 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 @@ -1,6 +1,7 @@ package jadx.core.dex.nodes; import java.io.StringWriter; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -9,23 +10,22 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.ClassData; -import com.android.dex.ClassData.Field; -import com.android.dex.ClassData.Method; -import com.android.dex.ClassDef; -import com.android.dex.Dex; - import jadx.api.ICodeCache; import jadx.api.ICodeInfo; +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.core.Consts; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.annotations.Annotation; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.nodes.NotificationAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.info.AccessInfo; @@ -35,12 +35,9 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.LiteralArg; -import jadx.core.dex.nodes.parser.AnnotationsParser; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.nodes.parser.SignatureParser; -import jadx.core.dex.nodes.parser.StaticValuesParser; import jadx.core.utils.SmaliUtils; -import jadx.core.utils.exceptions.DecodeException; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; @@ -49,8 +46,10 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); - private final DexNode dex; + private final RootNode root; private final int clsDefOffset; + private final Path inputPath; + private final ClassInfo clsInfo; private AccessInfo accessFlags; private ArgType superClass; @@ -74,63 +73,38 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN // cache maps private Map mthInfoMap = Collections.emptyMap(); - public ClassNode(DexNode dex, ClassDef cls) { - this.dex = dex; - this.clsDefOffset = cls.getOffset(); - this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); + public ClassNode(RootNode root, IClassData cls) { + this.root = root; + this.inputPath = cls.getInputPath(); + this.clsDefOffset = cls.getClassDefOffset(); + this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); try { - if (cls.getSupertypeIndex() == DexNode.NO_INDEX) { - this.superClass = null; + String superType = cls.getSuperType(); + if (superType == null) { // only java.lang.Object don't have super class if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) { throw new JadxRuntimeException("No super class in " + clsInfo.getType()); } + this.superClass = null; } else { - this.superClass = dex.getType(cls.getSupertypeIndex()); + this.superClass = ArgType.object(superType); } - this.interfaces = new ArrayList<>(cls.getInterfaces().length); - for (short interfaceIdx : cls.getInterfaces()) { - this.interfaces.add(dex.getType(interfaceIdx)); - } - if (cls.getClassDataOffset() != 0) { - ClassData clsData = dex.readClassData(cls); - int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length; - int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length; + this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object); - methods = new ArrayList<>(mthsCount); - fields = new ArrayList<>(fieldsCount); + methods = new ArrayList<>(); + fields = new ArrayList<>(); + cls.visitFieldsAndMethods( + fld -> fields.add(FieldNode.build(this, fld)), + mth -> methods.add(MethodNode.build(this, mth))); - for (Method mth : clsData.getDirectMethods()) { - methods.add(new MethodNode(this, mth, false)); - } - for (Method mth : clsData.getVirtualMethods()) { - methods.add(new MethodNode(this, mth, true)); - } - - for (Field f : clsData.getStaticFields()) { - fields.add(new FieldNode(this, f)); - } - loadStaticValues(cls, fields); - for (Field f : clsData.getInstanceFields()) { - fields.add(new FieldNode(this, f)); - } - } else { - methods = Collections.emptyList(); - fields = Collections.emptyList(); - } - - loadAnnotations(cls); + AnnotationsList.attach(this, cls.getAnnotations()); + loadStaticValues(cls, fields); initAccessFlags(cls); parseClassSignature(); setFieldsTypesFromSignature(); methods.forEach(MethodNode::initMethodTypes); - int sfIdx = cls.getSourceFileIndex(); - if (sfIdx != DexNode.NO_INDEX) { - String fileName = dex.getString(sfIdx); - addSourceFilenameAttr(fileName); - } - + addSourceFilenameAttr(cls.getSourceFile()); buildCache(); } catch (Exception e) { throw new JadxRuntimeException("Error decode class: " + clsInfo, e); @@ -140,11 +114,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN /** * Restore original access flags from Dalvik annotation if present */ - private void initAccessFlags(ClassDef cls) { + private void initAccessFlags(IClassData cls) { int accFlagsValue; - Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS); + IAnnotation a = getAnnotation(Consts.DALVIK_INNER_CLASS); if (a != null) { - accFlagsValue = (Integer) a.getValues().get("accessFlags"); + accFlagsValue = (Integer) a.getValues().get("accessFlags").getValue(); } else { accFlagsValue = cls.getAccessFlags(); } @@ -152,45 +126,37 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } // empty synthetic class - public ClassNode(DexNode dex, String name, int accessFlags) { - this.dex = dex; + public ClassNode(RootNode root, String name, int accessFlags) { + this.root = root; + this.inputPath = null; this.clsDefOffset = 0; - this.clsInfo = ClassInfo.fromName(dex.root(), name); + this.clsInfo = ClassInfo.fromName(root, name); this.interfaces = new ArrayList<>(); this.methods = new ArrayList<>(); this.fields = new ArrayList<>(); this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS); this.parentClass = this; - - dex.addClassNode(this); } - private void loadAnnotations(ClassDef cls) { - int offset = cls.getAnnotationsOffset(); - if (offset != 0) { - try { - new AnnotationsParser(this).parse(offset); - } catch (Exception e) { - LOG.error("Error parsing annotations in {}", this, e); - } + private void loadStaticValues(IClassData cls, List fields) { + if (fields.isEmpty()) { + return; } - } - - private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { + List staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList()); for (FieldNode f : staticFields) { if (f.getAccessFlags().isFinal()) { // incorrect initialization will be removed if assign found in constructor f.addAttr(FieldInitAttr.NULL_VALUE); } } - int offset = cls.getStaticValuesOffset(); - if (offset == 0) { + List values = cls.getStaticFieldInitValues(); + int count = values.size(); + if (count == 0 || count > staticFields.size()) { return; } - Dex.Section section = dex.openSection(offset); - StaticValuesParser parser = new StaticValuesParser(dex, section); - parser.processFields(staticFields); - + for (int i = 0; i < count; i++) { + staticFields.get(i).addAttr(FieldInitAttr.constValue(values.get(i))); + } // process const fields root().getConstValues().processConstFields(this, staticFields); } @@ -382,10 +348,6 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return root().getConstValues().getConstFieldByLiteralArg(this, arg); } - public FieldNode searchFieldById(int id) { - return searchField(FieldInfo.fromDex(dex, id)); - } - public FieldNode searchField(FieldInfo field) { for (FieldNode f : fields) { if (f.getFieldInfo().equals(field)) { @@ -441,14 +403,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return null; } - public MethodNode searchMethodById(int id) { - return searchMethodByShortId(MethodInfo.fromDex(dex, id).getShortId()); - } - public ClassNode getParentClass() { if (parentClass == null) { if (clsInfo.isInner()) { - ClassNode parent = dex().resolveClass(clsInfo.getParentClass()); + ClassNode parent = root.resolveClass(clsInfo.getParentClass()); parentClass = parent == null ? this : parent; } else { parentClass = this; @@ -545,14 +503,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.accessFlags = accessFlags; } - @Override - public DexNode dex() { - return dex; - } - @Override public RootNode root() { - return dex.root(); + return root; } @Override @@ -600,9 +553,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN } protected static boolean getSmali(ClassNode classNode, StringWriter stringWriter) { + Path inputPath = classNode.inputPath; + if (inputPath == null) { + stringWriter.append(String.format("###### Class %s is created by jadx", classNode.getFullName())); + return false; + } stringWriter.append(String.format("###### Class %s (%s)", classNode.getFullName(), classNode.getRawName())); stringWriter.append(System.lineSeparator()); - return SmaliUtils.getSmaliCode(classNode.dex, classNode.clsDefOffset, stringWriter); + return SmaliUtils.getSmaliCode(inputPath, classNode.clsDefOffset, stringWriter); } public ProcessState getState() { @@ -621,6 +579,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } + @Override + public Path getInputPath() { + return inputPath; + } + @Override public int hashCode() { return clsInfo.hashCode(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java deleted file mode 100644 index febb7a2ae..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/DexNode.java +++ /dev/null @@ -1,310 +0,0 @@ -package jadx.core.dex.nodes; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.android.dex.ClassData; -import com.android.dex.ClassData.Method; -import com.android.dex.ClassDef; -import com.android.dex.Code; -import com.android.dex.Dex; -import com.android.dex.Dex.Section; -import com.android.dex.FieldId; -import com.android.dex.MethodId; -import com.android.dex.ProtoId; -import com.android.dex.TypeList; - -import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.files.DexFile; - -public class DexNode implements IDexNode { - private static final Logger LOG = LoggerFactory.getLogger(DexNode.class); - - public static final int NO_INDEX = -1; - - private final RootNode root; - private final Dex dexBuf; - private final DexFile file; - private final int dexId; - - private final List classes = new ArrayList<>(); - private final Map clsMap = new HashMap<>(); - private final ArgType[] typesCache; - - public DexNode(RootNode root, DexFile input, int dexId) { - this.root = root; - this.file = input; - this.dexBuf = input.getDexBuf(); - this.dexId = dexId; - this.typesCache = new ArgType[dexBuf.typeIds().size()]; - } - - public void loadClasses() { - for (ClassDef cls : dexBuf.classDefs()) { - try { - addClassNode(new ClassNode(this, cls)); - } catch (Exception e) { - addDummyClass(cls, e); - } - } - // sort classes by name, expect top classes before inner - classes.sort(Comparator.comparing(ClassNode::getFullName)); - } - - private void addDummyClass(ClassDef classDef, Exception exc) { - int typeIndex = classDef.getTypeIndex(); - String name = null; - try { - ClassInfo clsInfo = ClassInfo.fromDex(this, typeIndex); - if (clsInfo != null) { - name = clsInfo.getShortName(); - } - } catch (Exception e) { - LOG.error("Failed to get name for class with type {}", typeIndex, e); - } - if (name == null || name.isEmpty()) { - name = "CLASS_" + typeIndex; - } - ClassNode clsNode = new ClassNode(this, name, classDef.getAccessFlags()); - ErrorsCounter.error(clsNode, "Load error", exc); - addClassNode(clsNode); - } - - public void addClassNode(ClassNode clsNode) { - classes.add(clsNode); - clsMap.put(clsNode.getClassInfo(), clsNode); - } - - void initInnerClasses() { - // move inner classes - List inner = new ArrayList<>(); - for (ClassNode cls : classes) { - if (cls.getClassInfo().isInner()) { - inner.add(cls); - } - } - List updated = new ArrayList<>(); - for (ClassNode cls : inner) { - ClassInfo clsInfo = cls.getClassInfo(); - ClassNode parent = resolveClass(clsInfo.getParentClass()); - if (parent == null) { - clsMap.remove(clsInfo); - clsInfo.notInner(root); - clsMap.put(clsInfo, cls); - updated.add(cls); - } else { - parent.addInnerClass(cls); - } - } - // reload names for inner classes of updated parents - for (ClassNode updCls : updated) { - for (ClassNode innerCls : updCls.getInnerClasses()) { - innerCls.getClassInfo().updateNames(root); - } - } - } - - public List getClasses() { - return classes; - } - - @Nullable - ClassNode resolveClassLocal(ClassInfo clsInfo) { - return clsMap.get(clsInfo); - } - - @Nullable - public ClassNode resolveClass(ClassInfo clsInfo) { - ClassNode classNode = resolveClassLocal(clsInfo); - if (classNode != null) { - return classNode; - } - return root.resolveClass(clsInfo); - } - - @Nullable - public ClassNode resolveClass(@NotNull ArgType type) { - if (type.isGeneric()) { - type = ArgType.object(type.getObject()); - } - return resolveClass(ClassInfo.fromType(root, type)); - } - - @Nullable - public MethodNode resolveMethod(@NotNull MethodInfo mth) { - ClassNode cls = resolveClass(mth.getDeclClass()); - if (cls != null) { - return cls.searchMethod(mth); - } - return null; - } - - @Nullable - MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) { - for (MethodNode m : cls.getMethods()) { - if (m.getMethodInfo().getShortId().startsWith(signature)) { - return m; - } - } - MethodNode found; - ArgType superClass = cls.getSuperClass(); - if (superClass != null) { - ClassNode superNode = resolveClass(superClass); - if (superNode != null) { - found = deepResolveMethod(superNode, signature); - if (found != null) { - return found; - } - } - } - for (ArgType iFaceType : cls.getInterfaces()) { - ClassNode iFaceNode = resolveClass(iFaceType); - if (iFaceNode != null) { - found = deepResolveMethod(iFaceNode, signature); - if (found != null) { - return found; - } - } - } - return null; - } - - @Nullable - public FieldNode resolveField(FieldInfo field) { - ClassNode cls = resolveClass(field.getDeclClass()); - if (cls != null) { - return cls.searchField(field); - } - return null; - } - - @Nullable - FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) { - FieldNode field = cls.searchFieldByNameAndType(fieldInfo); - if (field != null) { - return field; - } - ArgType superClass = cls.getSuperClass(); - if (superClass != null) { - ClassNode superNode = resolveClass(superClass); - if (superNode != null) { - FieldNode found = deepResolveField(superNode, fieldInfo); - if (found != null) { - return found; - } - } - } - for (ArgType iFaceType : cls.getInterfaces()) { - ClassNode iFaceNode = resolveClass(iFaceType); - if (iFaceNode != null) { - FieldNode found = deepResolveField(iFaceNode, fieldInfo); - if (found != null) { - return found; - } - } - } - return null; - } - - public DexFile getDexFile() { - return file; - } - - // DexBuffer wrappers - - public String getString(int index) { - if (index == DexNode.NO_INDEX) { - return null; - } - return dexBuf.strings().get(index); - } - - public ArgType getType(int index) { - if (index == DexNode.NO_INDEX) { - return null; - } - ArgType type = typesCache[index]; - if (type != null) { - return type; - } - // no synchronization because exactly one ArgType instance not needed, just reduce instances count - // note: same types but different instances will exist in other dex nodes - ArgType parsedType = ArgType.parse(getString(dexBuf.typeIds().get(index))); - typesCache[index] = parsedType; - return parsedType; - } - - public MethodId getMethodId(int mthIndex) { - return dexBuf.methodIds().get(mthIndex); - } - - public FieldId getFieldId(int fieldIndex) { - return dexBuf.fieldIds().get(fieldIndex); - } - - public ProtoId getProtoId(int protoIndex) { - return dexBuf.protoIds().get(protoIndex); - } - - public ClassData readClassData(ClassDef cls) { - return dexBuf.readClassData(cls); - } - - public List readParamList(int parametersOffset) { - TypeList paramList = dexBuf.readTypeList(parametersOffset); - List args = new ArrayList<>(paramList.getTypes().length); - for (short t : paramList.getTypes()) { - args.add(getType(t)); - } - return Collections.unmodifiableList(args); - } - - public Code readCode(Method mth) { - return dexBuf.readCode(mth); - } - - public Section openSection(int offset) { - return dexBuf.open(offset); - } - - public boolean checkOffset(int dataOffset) { - return dataOffset >= 0 && dataOffset < dexBuf.getLength(); - } - - @Override - public RootNode root() { - return root; - } - - @Override - public DexNode dex() { - return this; - } - - @Override - public String typeName() { - return "dex"; - } - - public int getDexId() { - return dexId; - } - - @Override - public String toString() { - return "DEX: " + file; - } -} 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 fa1d142b2..305a38e54 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 @@ -1,7 +1,9 @@ package jadx.core.dex.nodes; -import com.android.dex.ClassData.Field; +import java.nio.file.Path; +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; @@ -10,19 +12,21 @@ import jadx.core.dex.instructions.args.ArgType; public class FieldNode extends LineAttrNode implements ICodeNode { - private final ClassNode parent; + private final ClassNode parentClass; private final FieldInfo fieldInfo; private AccessInfo accFlags; private ArgType type; - public FieldNode(ClassNode cls, Field field) { - this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()), - field.getAccessFlags()); + public static FieldNode build(ClassNode cls, IFieldData fieldData) { + FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData); + FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags()); + AnnotationsList.attach(fieldNode, fieldData.getAnnotations()); + return fieldNode; } public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) { - this.parent = cls; + this.parentClass = cls; this.fieldInfo = fieldInfo; this.type = fieldInfo.getType(); this.accFlags = new AccessInfo(accessFlags, AFType.FIELD); @@ -42,6 +46,10 @@ public class FieldNode extends LineAttrNode implements ICodeNode { this.accFlags = accFlags; } + public boolean isStatic() { + return accFlags.isStatic(); + } + public String getName() { return fieldInfo.getName(); } @@ -59,7 +67,7 @@ public class FieldNode extends LineAttrNode implements ICodeNode { } public ClassNode getParentClass() { - return parent; + return parentClass; } @Override @@ -68,13 +76,13 @@ public class FieldNode extends LineAttrNode implements ICodeNode { } @Override - public DexNode dex() { - return parent.dex(); + public Path getInputPath() { + return parentClass.getInputPath(); } @Override public RootNode root() { - return parent.root(); + return parentClass.root(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java index 7e7dee5a5..c8ec7da4a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IDexNode.java @@ -1,10 +1,12 @@ package jadx.core.dex.nodes; +import java.nio.file.Path; + public interface IDexNode { String typeName(); - DexNode dex(); - RootNode root(); + + Path getInputPath(); } 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 bd579e737..e81b7cd55 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 @@ -8,8 +8,7 @@ import java.util.Objects; import org.jetbrains.annotations.Nullable; -import com.android.dx.io.instructions.DecodedInstruction; - +import jadx.api.plugins.input.insns.InsnData; import jadx.core.codegen.CodeWriter; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.nodes.LineAttrNode; @@ -171,7 +170,7 @@ public class InsnNode extends LineAttrNode { return -1; } - protected void addReg(DecodedInstruction insn, int i, ArgType type) { + protected void addReg(InsnData insn, int i, ArgType type) { addArg(InsnArg.reg(insn, i, type)); } @@ -183,7 +182,7 @@ public class InsnNode extends LineAttrNode { addArg(InsnArg.lit(literal, type)); } - protected void addLit(DecodedInstruction insn, ArgType type) { + protected void addLit(InsnData insn, ArgType type) { addArg(InsnArg.lit(insn, type)); } @@ -259,7 +258,6 @@ public class InsnNode extends LineAttrNode { case FILL_ARRAY: case FILLED_NEW_ARRAY: case NEW_ARRAY: - case NEW_MULTIDIM_ARRAY: case STR_CONCAT: return true; @@ -470,6 +468,9 @@ public class InsnNode extends LineAttrNode { } protected void appendArgs(StringBuilder sb) { + if (arguments.isEmpty()) { + return; + } String argsStr = Utils.listToString(arguments); if (argsStr.length() < 120) { sb.append(argsStr); 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 957fa0d89..7b3e2bffb 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 @@ -1,47 +1,39 @@ package jadx.core.dex.nodes; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Set; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dex.ClassData.Method; -import com.android.dex.Code; -import com.android.dex.Code.CatchHandler; -import com.android.dex.Code.Try; - +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.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.annotations.Annotation; -import jadx.core.dex.attributes.nodes.JumpInfo; +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; import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.instructions.GotoNode; -import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnDecoder; -import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.SwitchNode; 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.instructions.args.SSAVar; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.regions.Region; -import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -55,13 +47,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, private final ClassNode parentClass; private AccessInfo accFlags; - private final Method methodData; + private final ICodeReader codeReader; private final boolean methodIsVirtual; private boolean noCode; private int regsCount; - private int codeSize; - private int debugInfoOffset; private boolean loaded; @@ -82,13 +72,20 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, private List loops; private Region region; - public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) { - this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex()); + public static MethodNode build(ClassNode classNode, IMethodData methodData) { + MethodNode methodNode = new MethodNode(classNode, methodData); + AnnotationsList.attach(methodNode, methodData.getAnnotations()); + MethodParameters.attach(methodNode, methodData.getParamsAnnotations()); + return methodNode; + } + + public MethodNode(ClassNode classNode, IMethodData mthData) { + this.mthInfo = MethodInfo.fromData(classNode.root(), mthData); this.parentClass = classNode; this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD); - this.noCode = mthData.getCodeOffset() == 0; - this.methodData = noCode ? null : mthData; - this.methodIsVirtual = isVirtual; + this.noCode = mthData.getCodeReader() == null; + this.codeReader = noCode ? null : mthData.getCodeReader().copy(); + this.methodIsVirtual = !mthData.isDirect(); unload(); } @@ -122,26 +119,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, loaded = true; if (noCode) { regsCount = 0; - codeSize = 0; // TODO: registers not needed without code initArguments(this.argTypes); return; } - DexNode dex = parentClass.dex(); - Code mthCode = dex.readCode(methodData); - this.regsCount = mthCode.getRegistersSize(); + this.regsCount = codeReader.getRegistersCount(); initArguments(this.argTypes); - InsnDecoder decoder = new InsnDecoder(this); - decoder.decodeInsns(mthCode); - this.instructions = decoder.process(); - this.codeSize = instructions.length; - - initTryCatches(this, mthCode, instructions); - initJumps(instructions); - - this.debugInfoOffset = mthCode.getDebugInfoOffset(); + this.instructions = decoder.process(codeReader); } catch (Exception e) { if (!noCode) { unload(); @@ -331,130 +317,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return typeParameters; } - private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) { - CatchHandler[] catchBlocks = mthCode.getCatchHandlers(); - Try[] tries = mthCode.getTries(); - if (catchBlocks.length == 0 && tries.length == 0) { - return; - } - - int handlersCount = 0; - Set addrs = new HashSet<>(); - List catches = new ArrayList<>(catchBlocks.length); - - for (CatchHandler handler : catchBlocks) { - TryCatchBlock tcBlock = new TryCatchBlock(); - catches.add(tcBlock); - int[] handlerAddrArr = handler.getAddresses(); - for (int i = 0; i < handlerAddrArr.length; i++) { - int addr = handlerAddrArr[i]; - ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]); - tcBlock.addHandler(mth, addr, type); - addrs.add(addr); - handlersCount++; - } - int addr = handler.getCatchAllAddress(); - if (addr >= 0) { - tcBlock.addHandler(mth, addr, null); - addrs.add(addr); - handlersCount++; - } - } - - if (handlersCount > 0 && handlersCount != addrs.size()) { - // resolve nested try blocks: - // inner block contains all handlers from outer block => remove these handlers from inner block - // each handler must be only in one try/catch block - for (TryCatchBlock outerTry : catches) { - for (TryCatchBlock innerTry : catches) { - if (outerTry != innerTry - && innerTry.containsAllHandlers(outerTry)) { - innerTry.removeSameHandlers(outerTry); - } - } - } - } - - // attach EXC_HANDLER attributes to instructions - addrs.clear(); - for (TryCatchBlock ct : catches) { - for (ExceptionHandler eh : ct.getHandlers()) { - int addr = eh.getHandleOffset(); - ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh); - // TODO: don't override existing attribute - insnByOffset[addr].addAttr(ehAttr); - } - } - - // attach TRY_ENTER, TRY_LEAVE attributes to instructions - for (Try aTry : tries) { - int catchNum = aTry.getCatchHandlerIndex(); - TryCatchBlock catchBlock = catches.get(catchNum); - int offset = aTry.getStartAddress(); - int end = offset + aTry.getInstructionCount() - 1; - - boolean tryBlockStarted = false; - InsnNode insn = null; - while (offset <= end && offset >= 0) { - insn = insnByOffset[offset]; - if (insn != null && insn.getType() != InsnType.NOP) { - if (tryBlockStarted) { - catchBlock.addInsn(insn); - } else if (insn.canThrowException()) { - insn.add(AFlag.TRY_ENTER); - catchBlock.addInsn(insn); - tryBlockStarted = true; - } - } - offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - } - if (tryBlockStarted && insn != null) { - insn.add(AFlag.TRY_LEAVE); - } - } - } - - private static void initJumps(InsnNode[] insnByOffset) { - for (int offset = 0; offset < insnByOffset.length; offset++) { - InsnNode insn = insnByOffset[offset]; - if (insn == null) { - continue; - } - switch (insn.getType()) { - case SWITCH: - SwitchNode sw = (SwitchNode) insn; - for (int target : sw.getTargets()) { - addJump(insnByOffset, offset, target); - } - // default case - int nextInsnOffset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - if (nextInsnOffset != -1) { - addJump(insnByOffset, offset, nextInsnOffset); - } - break; - - case IF: - int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset); - if (next != -1) { - addJump(insnByOffset, offset, next); - } - addJump(insnByOffset, offset, ((IfNode) insn).getTarget()); - break; - - case GOTO: - addJump(insnByOffset, offset, ((GotoNode) insn).getTarget()); - break; - - default: - break; - } - } - } - - private static void addJump(InsnNode[] insnByOffset, int offset, int target) { - insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target)); - } - public String getName() { return mthInfo.getName(); } @@ -471,10 +333,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return noCode; } - public int getCodeSize() { - return codeSize; - } - public InsnNode[] getInstructions() { return instructions; } @@ -601,11 +459,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, @Override @SuppressWarnings("unchecked") public List getThrows() { - Annotation an = getAnnotation(Consts.DALVIK_THROWS); + IAnnotation an = getAnnotation(Consts.DALVIK_THROWS); if (an == null) { return Collections.emptyList(); } - return (List) an.getDefaultValue(); + List types = (List) an.getDefaultValue().getValue(); + return Utils.collectionMap(types, ev -> ArgType.object((String) ev.getValue())); } /** @@ -654,10 +513,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return regsCount; } - public int getDebugInfoOffset() { - return debugInfoOffset; - } - public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) { int regNum = assignArg.getRegNum(); return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg); @@ -709,14 +564,9 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, this.region = region; } - @Override - public DexNode dex() { - return parentClass.dex(); - } - @Override public RootNode root() { - return dex().root(); + return parentClass.root(); } @Override @@ -724,13 +574,23 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return "method"; } + @Override + public Path getInputPath() { + return parentClass.getInputPath(); + } + @Override public MethodInfo getMethodInfo() { return mthInfo; } public long getMethodCodeOffset() { - return noCode ? 0 : methodData.getCodeOffset(); + return noCode ? 0 : codeReader.getCodeOffset(); + } + + @Nullable + public IDebugInfo getDebugInfo() { + return noCode ? null : codeReader.getDebugInfo(); } /** @@ -756,6 +616,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return loaded; } + public ICodeReader getCodeReader() { + return codeReader; + } + @Override public int hashCode() { return mthInfo.hashCode(); 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 49c54759b..10ddbdac1 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 @@ -1,8 +1,10 @@ package jadx.core.dex.nodes; import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,6 +16,8 @@ import jadx.api.JadxArgs; import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.api.ResourcesLoader; +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; import jadx.core.Jadx; import jadx.core.clsp.ClspGraph; import jadx.core.dex.info.ClassInfo; @@ -31,8 +35,6 @@ import jadx.core.utils.ErrorsCounter; import jadx.core.utils.StringUtils; import jadx.core.utils.android.AndroidResourcesUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.utils.files.DexFile; -import jadx.core.utils.files.InputFile; import jadx.core.xmlgen.ResTableParser; import jadx.core.xmlgen.ResourceStorage; @@ -53,8 +55,10 @@ public class RootNode { private final ICodeCache codeCache; + private final List classes = new ArrayList<>(); + private final Map clsMap = new HashMap<>(); + private ClspGraph clsp; - private List dexNodes; @Nullable private String appPackage; @Nullable @@ -69,29 +73,47 @@ public class RootNode { this.codeCache = args.getCodeCache(); this.methodUtils = new MethodUtils(this); this.typeUtils = new TypeUtils(this); - - this.dexNodes = Collections.emptyList(); } - public void load(List inputFiles) { - dexNodes = new ArrayList<>(); - for (InputFile input : inputFiles) { - for (DexFile dexFile : input.getDexFiles()) { + public void loadClasses(List loadedInputs) { + for (ILoadResult loadedInput : loadedInputs) { + loadedInput.visitClasses(cls -> { try { - LOG.debug("Load: {}", dexFile); - DexNode dexNode = new DexNode(this, dexFile, dexNodes.size()); - dexNodes.add(dexNode); + addClassNode(new ClassNode(RootNode.this, cls)); } catch (Exception e) { - throw new JadxRuntimeException("Error decode file: " + dexFile, e); + addDummyClass(cls, e); } - } - } - for (DexNode dexNode : dexNodes) { - dexNode.loadClasses(); + }); } + // sort classes by name, expect top classes before inner + classes.sort(Comparator.comparing(ClassNode::getFullName)); initInnerClasses(); } + private void addDummyClass(IClassData classData, Exception exc) { + String typeStr = classData.getType(); + String name = null; + try { + ClassInfo clsInfo = ClassInfo.fromName(this, typeStr); + if (clsInfo != null) { + name = clsInfo.getShortName(); + } + } catch (Exception e) { + LOG.error("Failed to get name for class with type {}", typeStr, e); + } + if (name == null || name.isEmpty()) { + name = "CLASS_" + typeStr; + } + ClassNode clsNode = new ClassNode(this, name, classData.getAccessFlags()); + ErrorsCounter.error(clsNode, "Load error", exc); + addClassNode(clsNode); + } + + public void addClassNode(ClassNode clsNode) { + classes.add(clsNode); + clsMap.put(clsNode.getClassInfo(), clsNode); + } + public void loadResources(List resources) { ResourceFile arsc = null; for (ResourceFile rf : resources) { @@ -110,7 +132,9 @@ public class RootNode { parser.decode(is); return parser.getResStorage(); }); - processResources(resStorage); + if (resStorage != null) { + processResources(resStorage); + } } catch (Exception e) { LOG.error("Failed to parse '.arsc' file", e); } @@ -127,11 +151,6 @@ public class RootNode { if (this.clsp == null) { ClspGraph newClsp = new ClspGraph(this); newClsp.load(); - - List classes = new ArrayList<>(); - for (DexNode dexNode : dexNodes) { - classes.addAll(dexNode.getClasses()); - } newClsp.addApp(classes); this.clsp = newClsp; @@ -142,36 +161,54 @@ public class RootNode { } private void initInnerClasses() { - for (DexNode dexNode : dexNodes) { - dexNode.initInnerClasses(); + // move inner classes + List inner = new ArrayList<>(); + for (ClassNode cls : classes) { + if (cls.getClassInfo().isInner()) { + inner.add(cls); + } + } + List updated = new ArrayList<>(); + for (ClassNode cls : inner) { + ClassInfo clsInfo = cls.getClassInfo(); + ClassNode parent = resolveClass(clsInfo.getParentClass()); + if (parent == null) { + clsMap.remove(clsInfo); + clsInfo.notInner(this); + clsMap.put(clsInfo, cls); + updated.add(cls); + } else { + parent.addInnerClass(cls); + } + } + // reload names for inner classes of updated parents + for (ClassNode updCls : updated) { + for (ClassNode innerCls : updCls.getInnerClasses()) { + innerCls.getClassInfo().updateNames(this); + } } } + public List getClasses() { + return classes; + } + public List getClasses(boolean includeInner) { - List classes = new ArrayList<>(); - for (DexNode dex : dexNodes) { - if (includeInner) { - classes.addAll(dex.getClasses()); - } else { - for (ClassNode cls : dex.getClasses()) { - if (!cls.getClassInfo().isInner()) { - classes.add(cls); - } - } + if (includeInner) { + return classes; + } + List notInnerClasses = new ArrayList<>(); + for (ClassNode cls : classes) { + if (!cls.getClassInfo().isInner()) { + notInnerClasses.add(cls); } } - return classes; + return notInnerClasses; } @Nullable public ClassNode resolveClass(ClassInfo clsInfo) { - for (DexNode dexNode : dexNodes) { - ClassNode cls = dexNode.resolveClassLocal(clsInfo); - if (cls != null) { - return cls; - } - } - return null; + return clsMap.get(clsInfo); } @Nullable @@ -185,30 +222,22 @@ public class RootNode { if (clsType.isGeneric()) { clsType = ArgType.object(clsType.getObject()); } - for (DexNode dexNode : dexNodes) { - ClassNode cls = dexNode.resolveClass(clsType); - if (cls != null) { - return cls; - } - } - return null; + return resolveClass(ClassInfo.fromType(this, clsType)); } @Nullable - public ClassNode searchClassByName(String fullName) { + public ClassNode resolveClass(String fullName) { ClassInfo clsInfo = ClassInfo.fromName(this, fullName); return resolveClass(clsInfo); } @Nullable public ClassNode searchClassByFullAlias(String fullName) { - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - ClassInfo classInfo = cls.getClassInfo(); - if (classInfo.getFullName().equals(fullName) - || classInfo.getAliasFullName().equals(fullName)) { - return cls; - } + for (ClassNode cls : classes) { + ClassInfo classInfo = cls.getClassInfo(); + if (classInfo.getFullName().equals(fullName) + || classInfo.getAliasFullName().equals(fullName)) { + return cls; } } return null; @@ -216,16 +245,23 @@ public class RootNode { public List searchClassByShortName(String shortName) { List list = new ArrayList<>(); - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) { - if (cls.getClassInfo().getShortName().equals(shortName)) { - list.add(cls); - } + for (ClassNode cls : classes) { + if (cls.getClassInfo().getShortName().equals(shortName)) { + list.add(cls); } } return list; } + @Nullable + public MethodNode resolveMethod(@NotNull MethodInfo mth) { + ClassNode cls = resolveClass(mth.getDeclClass()); + if (cls != null) { + return cls.searchMethod(mth); + } + return null; + } + @Nullable public MethodNode deepResolveMethod(@NotNull MethodInfo mth) { ClassNode cls = resolveClass(mth.getDeclClass()); @@ -236,7 +272,46 @@ public class RootNode { if (methodNode != null) { return methodNode; } - return cls.dex().deepResolveMethod(cls, mth.makeSignature(false)); + return deepResolveMethod(cls, mth.makeSignature(false)); + } + + @Nullable + private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) { + for (MethodNode m : cls.getMethods()) { + if (m.getMethodInfo().getShortId().startsWith(signature)) { + return m; + } + } + MethodNode found; + ArgType superClass = cls.getSuperClass(); + if (superClass != null) { + ClassNode superNode = resolveClass(superClass); + if (superNode != null) { + found = deepResolveMethod(superNode, signature); + if (found != null) { + return found; + } + } + } + for (ArgType iFaceType : cls.getInterfaces()) { + ClassNode iFaceNode = resolveClass(iFaceType); + if (iFaceNode != null) { + found = deepResolveMethod(iFaceNode, signature); + if (found != null) { + return found; + } + } + } + return null; + } + + @Nullable + public FieldNode resolveField(FieldInfo field) { + ClassNode cls = resolveClass(field.getDeclClass()); + if (cls != null) { + return cls.searchField(field); + } + return null; } @Nullable @@ -245,7 +320,35 @@ public class RootNode { if (cls == null) { return null; } - return cls.dex().deepResolveField(cls, field); + return deepResolveField(cls, field); + } + + @Nullable + private FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) { + FieldNode field = cls.searchFieldByNameAndType(fieldInfo); + if (field != null) { + return field; + } + ArgType superClass = cls.getSuperClass(); + if (superClass != null) { + ClassNode superNode = resolveClass(superClass); + if (superNode != null) { + FieldNode found = deepResolveField(superNode, fieldInfo); + if (found != null) { + return found; + } + } + } + for (ArgType iFaceType : cls.getInterfaces()) { + ClassNode iFaceNode = resolveClass(iFaceType); + if (iFaceNode != null) { + FieldNode found = deepResolveField(iFaceNode, fieldInfo); + if (found != null) { + return found; + } + } + } + return null; } public List getPasses() { @@ -262,10 +365,6 @@ public class RootNode { } } - public List getDexNodes() { - return dexNodes; - } - public ClspGraph getClsp() { return clsp; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java deleted file mode 100644 index e2b34e8bc..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/AnnotationsParser.java +++ /dev/null @@ -1,112 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import com.android.dex.Dex.Section; - -import jadx.core.dex.attributes.annotations.Annotation; -import jadx.core.dex.attributes.annotations.Annotation.Visibility; -import jadx.core.dex.attributes.annotations.AnnotationsList; -import jadx.core.dex.attributes.annotations.MethodParameters; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.DecodeException; - -public class AnnotationsParser { - - private static final Visibility[] VISIBILITIES = { - Visibility.BUILD, - Visibility.RUNTIME, - Visibility.SYSTEM - }; - - private final DexNode dex; - private final ClassNode cls; - - public AnnotationsParser(ClassNode cls) { - this.cls = cls; - this.dex = cls.dex(); - } - - public void parse(int offset) throws DecodeException { - Section section = dex.openSection(offset); - - // TODO read as unsigned int - int classAnnotationsOffset = section.readInt(); - int fieldsCount = section.readInt(); - int annotatedMethodsCount = section.readInt(); - int annotatedParametersCount = section.readInt(); - - if (classAnnotationsOffset != 0) { - cls.addAttr(readAnnotationSet(classAnnotationsOffset)); - } - - for (int i = 0; i < fieldsCount; i++) { - FieldNode f = cls.searchFieldById(section.readInt()); - f.addAttr(readAnnotationSet(section.readInt())); - } - - for (int i = 0; i < annotatedMethodsCount; i++) { - MethodNode m = cls.searchMethodById(section.readInt()); - m.addAttr(readAnnotationSet(section.readInt())); - } - - for (int i = 0; i < annotatedParametersCount; i++) { - MethodNode mth = cls.searchMethodById(section.readInt()); - // read annotation ref list - Section ss = dex.openSection(section.readInt()); - int size = ss.readInt(); - MethodParameters params = new MethodParameters(size); - for (int j = 0; j < size; j++) { - params.getParamList().add(readAnnotationSet(ss.readInt())); - } - mth.addAttr(params); - } - } - - private AnnotationsList readAnnotationSet(int offset) throws DecodeException { - if (offset == 0) { - return AnnotationsList.EMPTY; - } - Section section = dex.openSection(offset); - int size = section.readInt(); - if (size == 0) { - return AnnotationsList.EMPTY; - } - List list = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - Section anSection = dex.openSection(section.readInt()); - Annotation a = readAnnotation(dex, anSection, true); - list.add(a); - } - return new AnnotationsList(list); - } - - public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException { - EncValueParser parser = new EncValueParser(dex, s); - Visibility visibility = null; - if (readVisibility) { - byte v = s.readByte(); - visibility = VISIBILITIES[v]; - } - int typeIndex = s.readUleb128(); - int size = s.readUleb128(); - Map values = new LinkedHashMap<>(size); - for (int i = 0; i < size; i++) { - String name = dex.getString(s.readUleb128()); - values.put(name, parser.parseValue()); - } - ArgType type = dex.getType(typeIndex); - Annotation annotation = new Annotation(visibility, type, values); - if (!type.isObject()) { - throw new DecodeException("Incorrect type for annotation: " + annotation); - } - return annotation; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java deleted file mode 100644 index f1c3ba622..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/EncValueParser.java +++ /dev/null @@ -1,132 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.ArrayList; -import java.util.List; - -import com.android.dex.Dex.Section; -import com.android.dex.Leb128; - -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.nodes.DexNode; -import jadx.core.utils.exceptions.DecodeException; - -public class EncValueParser { - - private static final int ENCODED_BYTE = 0x00; - private static final int ENCODED_SHORT = 0x02; - private static final int ENCODED_CHAR = 0x03; - private static final int ENCODED_INT = 0x04; - private static final int ENCODED_LONG = 0x06; - private static final int ENCODED_FLOAT = 0x10; - private static final int ENCODED_DOUBLE = 0x11; - private static final int ENCODED_STRING = 0x17; - private static final int ENCODED_TYPE = 0x18; - private static final int ENCODED_FIELD = 0x19; - private static final int ENCODED_ENUM = 0x1b; - private static final int ENCODED_METHOD = 0x1a; - private static final int ENCODED_ARRAY = 0x1c; - private static final int ENCODED_ANNOTATION = 0x1d; - private static final int ENCODED_NULL = 0x1e; - private static final int ENCODED_BOOLEAN = 0x1f; - - protected final Section in; - - private final DexNode dex; - - public EncValueParser(DexNode dex, Section in) { - this.in = in; - this.dex = dex; - } - - public Object parseValue() throws DecodeException { - int argAndType = readByte(); - int type = argAndType & 0x1F; - int arg = (argAndType & 0xE0) >> 5; - int size = arg + 1; - - switch (type) { - case ENCODED_NULL: - return null; - - case ENCODED_BOOLEAN: - return arg == 1; - case ENCODED_BYTE: - return in.readByte(); - - case ENCODED_SHORT: - return (short) parseNumber(size, true); - case ENCODED_CHAR: - return (char) parseUnsignedInt(size); - case ENCODED_INT: - return (int) parseNumber(size, true); - case ENCODED_LONG: - return parseNumber(size, true); - - case ENCODED_FLOAT: - return Float.intBitsToFloat((int) parseNumber(size, false, 4)); - case ENCODED_DOUBLE: - return Double.longBitsToDouble(parseNumber(size, false, 8)); - - case ENCODED_STRING: - return dex.getString(parseUnsignedInt(size)); - - case ENCODED_TYPE: - return dex.getType(parseUnsignedInt(size)); - - case ENCODED_METHOD: - return MethodInfo.fromDex(dex, parseUnsignedInt(size)); - - case ENCODED_FIELD: - case ENCODED_ENUM: - return FieldInfo.fromDex(dex, parseUnsignedInt(size)); - - case ENCODED_ARRAY: - int count = Leb128.readUnsignedLeb128(in); - List values = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - values.add(parseValue()); - } - return values; - - case ENCODED_ANNOTATION: - return AnnotationsParser.readAnnotation(dex, in, false); - - default: - throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type)); - } - } - - private int parseUnsignedInt(int byteCount) { - return (int) parseNumber(byteCount, false, 0); - } - - private long parseNumber(int byteCount, boolean isSignExtended) { - return parseNumber(byteCount, isSignExtended, 0); - } - - private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) { - long result = 0; - long last = 0; - for (int i = 0; i < byteCount; i++) { - last = readByte(); - result |= last << i * 8; - } - if (fillOnRight != 0) { - for (int i = byteCount; i < fillOnRight; i++) { - result <<= 8; - } - } else { - if (isSignExtended && (last & 0x80) != 0) { - for (int i = byteCount; i < 8; i++) { - result |= (long) 0xFF << i * 8; - } - } - } - return result; - } - - private int readByte() { - return in.readByte() & 0xFF; - } -} 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 f4adb4dab..870aa32b5 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,11 +9,13 @@ 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.core.dex.attributes.IAttributeNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.GenericTypeParameter; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; public class SignatureParser { @@ -45,11 +47,13 @@ public class SignatureParser { @SuppressWarnings("unchecked") @Nullable public static String getSignature(IAttributeNode node) { - Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE); + IAnnotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE); if (a == null) { return null; } - return mergeSignature((List) a.getDefaultValue()); + List values = (List) a.getDefaultValue().getValue(); + List strings = Utils.collectionMap(values, ev -> ((String) ev.getValue())); + return mergeSignature(strings); } private char next() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java deleted file mode 100644 index 5f10bdd29..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/StaticValuesParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package jadx.core.dex.nodes.parser; - -import java.util.List; - -import com.android.dex.Dex.Section; -import com.android.dex.Leb128; - -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.utils.exceptions.DecodeException; - -public class StaticValuesParser extends EncValueParser { - - public StaticValuesParser(DexNode dex, Section in) { - super(dex, in); - } - - public int processFields(List fields) throws DecodeException { - int count = Leb128.readUnsignedLeb128(in); - for (int i = 0; i < count; i++) { - Object value = parseValue(); - if (i < fields.size()) { - fields.get(i).addAttr(FieldInitAttr.constValue(value)); - } - } - return count; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java index e39b64e09..ccb978dbc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java @@ -2,7 +2,6 @@ package jadx.core.dex.trycatch; import java.util.ArrayList; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import org.jetbrains.annotations.Nullable; @@ -24,8 +23,8 @@ public class TryCatchBlock { private final List insns; private final CatchAttr attr; - public TryCatchBlock() { - handlers = new LinkedList<>(); + public TryCatchBlock(int handlersCount) { + handlers = new ArrayList<>(handlersCount); insns = new ArrayList<>(); attr = new CatchAttr(this); } 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 new file mode 100644 index 000000000..943d795fb --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java @@ -0,0 +1,135 @@ +package jadx.core.dex.visitors; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.ITry; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.trycatch.ExcHandlerAttr; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.utils.exceptions.JadxException; + +import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset; + +@JadxVisitor( + name = "Attach Try/Catch Visitor", + desc = "Attach try/catch info to instructions", + runBefore = { + ProcessInstructionsVisitor.class + } +) +public class AttachTryCatchVisitor extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + initTryCatches(mth, mth.getCodeReader(), mth.getInstructions()); + } + + private static void initTryCatches(MethodNode mth, ICodeReader codeReader, InsnNode[] insnByOffset) { + List tries = codeReader.getTries(); + if (tries.isEmpty()) { + return; + } + int handlersCount = 0; + Set addrs = new HashSet<>(); + List catches = new ArrayList<>(tries.size()); + for (ITry tryData : tries) { + TryCatchBlock catchBlock = processHandlers(mth, addrs, tryData.getCatch()); + catches.add(catchBlock); + handlersCount += catchBlock.getHandlersCount(); + } + + // TODO: run modify in later passes + if (handlersCount > 0 && handlersCount != addrs.size()) { + // resolve nested try blocks: + // inner block contains all handlers from outer block => remove these handlers from inner block + // each handler must be only in one try/catch block + for (TryCatchBlock outerTry : catches) { + for (TryCatchBlock innerTry : catches) { + if (outerTry != innerTry + && innerTry.containsAllHandlers(outerTry)) { + innerTry.removeSameHandlers(outerTry); + } + } + } + } + addrs.clear(); + + for (TryCatchBlock tryCatchBlock : catches) { + if (tryCatchBlock.getHandlersCount() == 0) { + continue; + } + for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { + int addr = handler.getHandleOffset(); + ExcHandlerAttr ehAttr = new ExcHandlerAttr(tryCatchBlock, handler); + // TODO: don't override existing attribute + insnByOffset[addr].addAttr(ehAttr); + } + } + + int k = 0; + for (ITry tryData : tries) { + TryCatchBlock catchBlock = catches.get(k++); + if (catchBlock.getHandlersCount() != 0) { + markTryBounds(insnByOffset, tryData, catchBlock); + } + } + + } + + private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, TryCatchBlock catchBlock) { + int offset = aTry.getStartAddress(); + int end = offset + aTry.getInstructionCount() - 1; + + boolean tryBlockStarted = false; + InsnNode insn = null; + while (offset <= end && offset >= 0) { + insn = insnByOffset[offset]; + if (insn != null && insn.getType() != InsnType.NOP) { + if (tryBlockStarted) { + catchBlock.addInsn(insn); + } else if (insn.canThrowException()) { + insn.add(AFlag.TRY_ENTER); + catchBlock.addInsn(insn); + tryBlockStarted = true; + } + } + offset = getNextInsnOffset(insnByOffset, offset); + } + if (tryBlockStarted && insn != null) { + insn.add(AFlag.TRY_LEAVE); + } + } + + private static TryCatchBlock processHandlers(MethodNode mth, Set addrs, ICatch catchBlock) { + int[] handlerAddrArr = catchBlock.getAddresses(); + String[] handlerTypes = catchBlock.getTypes(); + + int handlersCount = handlerAddrArr.length; + TryCatchBlock tcBlock = new TryCatchBlock(handlersCount); + for (int i = 0; i < handlersCount; i++) { + int addr = handlerAddrArr[i]; + ClassInfo type = ClassInfo.fromName(mth.root(), handlerTypes[i]); + tcBlock.addHandler(mth, addr, type); + addrs.add(addr); + } + int addr = catchBlock.getCatchAllAddress(); + if (addr >= 0) { + tcBlock.addHandler(mth, addr, null); + addrs.add(addr); + } + return tcBlock; + } +} 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 508adb3e3..abdd2e357 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 @@ -4,8 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -80,7 +79,7 @@ public class ClassModifier extends AbstractVisitor { for (FieldNode field : cls.getFields()) { if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); - ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); + ClassNode fieldsCls = cls.root().resolveClass(clsInfo); ClassInfo parentClass = cls.getClassInfo().getParentClass(); if (fieldsCls != null && (inline || parentClass.equals(fieldsCls.getClassInfo()))) { @@ -171,7 +170,7 @@ public class ClassModifier extends AbstractVisitor { if (!argType.isObject()) { continue; } - ClassNode argCls = cls.dex().resolveClass(argType); + ClassNode argCls = cls.root().resolveClass(argType); if (argCls == null) { // check if missing class from current top class ClassInfo argClsInfo = ClassInfo.fromType(cls.root(), argType); @@ -268,7 +267,7 @@ public class ClassModifier extends AbstractVisitor { // remove confirmed, change visibility and name if needed if (!wrappedAccFlags.isPublic()) { // must be public - FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC); + FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.PUBLIC); } String alias = mth.getAlias(); if (!Objects.equals(wrappedMth.getAlias(), alias)) { @@ -341,7 +340,7 @@ public class ClassModifier extends AbstractVisitor { } } else if (type == InsnType.IPUT) { FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode fieldNode = mth.dex().resolveField(fldInfo); + FieldNode fieldNode = mth.root().resolveField(fldInfo); if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) { insn.add(AFlag.DONT_GENERATE); } 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 90593ca6f..388ca08a8 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 @@ -114,13 +114,13 @@ public class ConstructorVisitor extends AbstractVisitor { */ @Nullable private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) { - MethodNode callMth = mth.dex().resolveMethod(co.getCallMth()); + MethodNode callMth = mth.root().resolveMethod(co.getCallMth()); if (callMth == null || !callMth.getAccessFlags().isSynthetic() || !allArgsNull(co)) { return null; } - ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo()); + ClassNode classNode = mth.root().resolveClass(callMth.getParentClass().getClassInfo()); if (classNode == null) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java index 5f59449ab..0e4b4b4c7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java @@ -7,33 +7,32 @@ import java.util.List; import java.util.Set; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.IndexInsnNode; -import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxException; public class DependencyCollector extends AbstractVisitor { @Override public boolean visit(ClassNode cls) throws JadxException { - DexNode dex = cls.dex(); + RootNode root = cls.root(); Set depSet = new HashSet<>(); - processClass(cls, dex, depSet); + processClass(cls, root, depSet); for (ClassNode inner : cls.getInnerClasses()) { - processClass(inner, dex, depSet); + processClass(inner, root, depSet); } depSet.remove(cls); @@ -43,18 +42,18 @@ public class DependencyCollector extends AbstractVisitor { return false; } - private static void processClass(ClassNode cls, DexNode dex, Set depList) { - addDep(dex, depList, cls.getSuperClass()); + private static void processClass(ClassNode cls, RootNode root, Set depList) { + addDep(root, depList, cls.getSuperClass()); for (ArgType iType : cls.getInterfaces()) { - addDep(dex, depList, iType); + addDep(root, depList, iType); } for (FieldNode fieldNode : cls.getFields()) { - addDep(dex, depList, fieldNode.getType()); + addDep(root, depList, fieldNode.getType()); // process instructions from field init FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT); if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) { - processInsn(dex, depList, fieldInitAttr.getInsn()); + processInsn(root, depList, fieldInitAttr.getInsn()); } } // TODO: process annotations and generics @@ -62,80 +61,77 @@ public class DependencyCollector extends AbstractVisitor { if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) { continue; } - processMethod(dex, depList, methodNode); + processMethod(root, depList, methodNode); } } - private static void processMethod(DexNode dex, Set depList, MethodNode methodNode) { - addDep(dex, depList, methodNode.getParentClass()); - addDep(dex, depList, methodNode.getReturnType()); + private static void processMethod(RootNode root, Set depList, MethodNode methodNode) { + addDep(root, depList, methodNode.getParentClass()); + addDep(root, depList, methodNode.getReturnType()); for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) { - addDep(dex, depList, arg); + addDep(root, depList, arg); } for (BlockNode block : methodNode.getBasicBlocks()) { for (InsnNode insnNode : block.getInstructions()) { - processInsn(dex, depList, insnNode); + processInsn(root, depList, insnNode); } } } // TODO: add custom instructions processing - private static void processInsn(DexNode dex, Set depList, InsnNode insnNode) { + private static void processInsn(RootNode root, Set depList, InsnNode insnNode) { RegisterArg result = insnNode.getResult(); if (result != null) { - addDep(dex, depList, result.getType()); + addDep(root, depList, result.getType()); } for (InsnArg arg : insnNode.getArguments()) { if (arg.isInsnWrap()) { - processInsn(dex, depList, ((InsnWrapArg) arg).getWrapInsn()); + processInsn(root, depList, ((InsnWrapArg) arg).getWrapInsn()); } else { - addDep(dex, depList, arg.getType()); + addDep(root, depList, arg.getType()); } } - processCustomInsn(dex, depList, insnNode); + processCustomInsn(root, depList, insnNode); } - private static void processCustomInsn(DexNode dex, Set depList, InsnNode insn) { + private static void processCustomInsn(RootNode root, Set depList, InsnNode insn) { if (insn instanceof IndexInsnNode) { Object index = ((IndexInsnNode) insn).getIndex(); if (index instanceof FieldInfo) { - addDep(dex, depList, ((FieldInfo) index).getDeclClass()); + addDep(root, depList, ((FieldInfo) index).getDeclClass()); } else if (index instanceof ArgType) { - addDep(dex, depList, (ArgType) index); + addDep(root, depList, (ArgType) index); } - } else if (insn instanceof InvokeNode) { - ClassInfo declClass = ((InvokeNode) insn).getCallMth().getDeclClass(); - addDep(dex, depList, declClass); - } else if (insn instanceof ConstructorInsn) { - ClassInfo declClass = ((ConstructorInsn) insn).getCallMth().getDeclClass(); - addDep(dex, depList, declClass); + } else if (insn instanceof BaseInvokeNode) { + ClassInfo declClass = ((BaseInvokeNode) insn).getCallMth().getDeclClass(); + addDep(root, depList, declClass); } } - private static void addDep(DexNode dex, Set depList, ArgType type) { + private static void addDep(RootNode root, Set depList, ArgType type) { if (type != null) { if (type.isObject() && !type.isGenericType()) { - addDep(dex, depList, ClassInfo.fromType(dex.root(), type)); + addDep(root, depList, ClassInfo.fromType(root, type)); ArgType[] genericTypes = type.getGenericTypes(); if (type.isGeneric() && genericTypes != null) { for (ArgType argType : genericTypes) { - addDep(dex, depList, argType); + addDep(root, depList, argType); } } } else if (type.isArray()) { - addDep(dex, depList, type.getArrayRootElement()); + addDep(root, depList, type.getArrayRootElement()); } } } - private static void addDep(DexNode dex, Set depList, ClassInfo clsInfo) { + private static void addDep(RootNode root, Set depList, ClassInfo clsInfo) { if (clsInfo != null) { - ClassNode node = dex.resolveClass(clsInfo); - addDep(dex, depList, node); + ClassNode node = root.resolveClass(clsInfo); + addDep(root, depList, node); } } - private static void addDep(DexNode dex, Set depList, ClassNode clsNode) { + private static void addDep(RootNode root, Set depList, ClassNode clsNode) { if (clsNode != null) { // add only top classes depList.add(clsNode.getTopParentClass()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java index a8ef3ca78..76235c25f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DepthTraversal.java @@ -1,6 +1,5 @@ package jadx.core.dex.visitors; -import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.DebugChecks; @@ -19,9 +18,6 @@ public class DepthTraversal { } public static void visit(IDexTreeVisitor visitor, MethodNode mth) { - if (mth.contains(AType.JADX_ERROR)) { - return; - } try { visitor.visit(mth); if (DebugChecks.checksEnabled) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 2a81bfa0c..7b9b2ca1f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -11,8 +11,7 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -36,7 +35,6 @@ import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; @@ -76,7 +74,7 @@ public class EnumVisitor extends AbstractVisitor { if (!convertToEnum(cls)) { AccessInfo accessFlags = cls.getAccessFlags(); if (accessFlags.isEnum()) { - cls.setAccessFlags(accessFlags.remove(AccessFlags.ACC_ENUM)); + cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM)); cls.addAttr(AType.COMMENTS, "JADX INFO: Failed to restore enum class, 'enum' modifier removed"); } } @@ -162,7 +160,7 @@ public class EnumVisitor extends AbstractVisitor { FieldNode fieldNode = enumField.getField(); // use string arg from the constructor as enum field name - String name = getConstString(cls.dex(), co.getArg(0)); + String name = getConstString(cls.root(), co.getArg(0)); if (name != null && !fieldNode.getAlias().equals(name) && NameMapper.isValidAndPrintable(name) @@ -196,7 +194,7 @@ public class EnumVisitor extends AbstractVisitor { if (!regs.isEmpty()) { cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect"); } - MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); if (ctrMth != null) { markArgsForSkip(ctrMth); } @@ -293,14 +291,14 @@ public class EnumVisitor extends AbstractVisitor { return null; } ClassInfo clsInfo = co.getClassType(); - ClassNode constrCls = cls.dex().resolveClass(clsInfo); + ClassNode constrCls = cls.root().resolveClass(clsInfo); if (constrCls == null) { return null; } if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { return null; } - MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth()); + MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth()); if (ctrMth == null) { return null; } @@ -415,10 +413,10 @@ public class EnumVisitor extends AbstractVisitor { return null; } - private String getConstString(DexNode dex, InsnArg arg) { + private String getConstString(RootNode root, InsnArg arg) { if (arg.isInsnWrap()) { InsnNode constInsn = ((InsnWrapArg) arg).getWrapInsn(); - Object constValue = InsnUtils.getConstValueByInsn(dex, constInsn); + Object constValue = InsnUtils.getConstValueByInsn(root, constInsn); if (constValue instanceof String) { return (String) constValue; } 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 d219f9979..9f518fe15 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 @@ -8,6 +8,7 @@ import java.util.Set; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.IndexInsnNode; @@ -20,7 +21,6 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.utils.BlockUtils; import jadx.core.utils.InsnRemover; import jadx.core.utils.exceptions.JadxException; @@ -155,7 +155,7 @@ public class ExtractFieldInit extends AbstractVisitor { Set fields = new HashSet<>(); for (InsnNode insn : common.getPutInsns()) { FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode field = cls.dex().resolveField(fieldInfo); + FieldNode field = cls.root().resolveField(fieldInfo); if (field == null) { return; } @@ -175,7 +175,7 @@ public class ExtractFieldInit extends AbstractVisitor { } for (InsnNode insn : common.getPutInsns()) { FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode field = cls.dex().resolveField(fieldInfo); + FieldNode field = cls.root().resolveField(fieldInfo); addFieldInitAttr(common.getConstrMth(), field, insn); } } @@ -202,7 +202,7 @@ public class ExtractFieldInit extends AbstractVisitor { // exclude fields from super classes return false; } - FieldNode fieldNode = cls.dex().resolveField(fieldInfo); + FieldNode fieldNode = cls.root().resolveField(fieldInfo); if (fieldNode == null) { // exclude inherited fields (not declared in this class) return false; 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 7156857bc..322350f18 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,7 +1,6 @@ package jadx.core.dex.visitors; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.nodes.ICodeNode; @@ -45,12 +44,12 @@ public class FixAccessModifiers extends AbstractVisitor { private static int fixVisibility(MethodNode mth) { if (mth.isVirtual()) { // make virtual methods public - return AccessFlags.ACC_PUBLIC; + return AccessFlags.PUBLIC; } else { AccessInfo accessFlags = mth.getAccessFlags(); if (accessFlags.isAbstract()) { // make abstract methods public - return AccessFlags.ACC_PUBLIC; + return AccessFlags.PUBLIC; } // enum constructor can't be public if (accessFlags.isConstructor() @@ -63,7 +62,7 @@ public class FixAccessModifiers extends AbstractVisitor { return -1; } // make other direct methods private - return AccessFlags.ACC_PRIVATE; + return AccessFlags.PRIVATE; } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java index f58e0391e..f638bbd3d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/GenericTypesVisitor.java @@ -49,7 +49,7 @@ public class GenericTypesVisitor extends AbstractVisitor { if (argType == null || argType.getGenericTypes() == null) { return; } - ClassNode cls = mth.dex().resolveClass(insn.getClassType()); + ClassNode cls = mth.root().resolveClass(insn.getClassType()); if (cls != null && cls.getGenericTypeParameters().isEmpty()) { return; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index 993b65fb1..aecd0b4a8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -3,8 +3,7 @@ package jadx.core.dex.visitors; import java.util.ArrayList; import java.util.List; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -87,7 +86,7 @@ public class MethodInlineVisitor extends AbstractVisitor { } private static boolean fixVisibilityOfInlineCode(MethodNode mth, InsnNode insn) { - int newVisFlag = AccessFlags.ACC_PUBLIC; // TODO: calculate more precisely + int newVisFlag = AccessFlags.PUBLIC; // TODO: calculate more precisely InsnType insnType = insn.getType(); if (insnType == InsnType.INVOKE) { InvokeNode invoke = (InvokeNode) insn; 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 03b76533f..93388a8ed 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 @@ -9,10 +9,13 @@ import org.jetbrains.annotations.Nullable; 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.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; -import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.annotations.AnnotationsList; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.info.FieldInfo; @@ -20,14 +23,14 @@ import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; -import jadx.core.dex.instructions.FillArrayNode; +import jadx.core.dex.instructions.FillArrayInsn; import jadx.core.dex.instructions.FilledNewArrayNode; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; @@ -108,7 +111,7 @@ public class ModVisitor extends AbstractVisitor { break; case SWITCH: - replaceConstKeys(parentClass, (SwitchNode) insn); + replaceConstKeys(parentClass, (SwitchInsn) insn); break; case NEW_ARRAY: @@ -116,7 +119,7 @@ public class ModVisitor extends AbstractVisitor { NewArrayNode newArrInsn = (NewArrayNode) insn; InsnNode nextInsn = getFirstUseSkipMove(insn.getResult()); if (nextInsn != null && nextInsn.getType() == InsnType.FILL_ARRAY) { - FillArrayNode fillArrInsn = (FillArrayNode) nextInsn; + FillArrayInsn fillArrInsn = (FillArrayInsn) nextInsn; if (checkArrSizes(mth, newArrInsn, fillArrInsn)) { InsnNode filledArr = makeFilledArrayInsn(mth, newArrInsn, fillArrInsn); replaceInsn(mth, block, i, filledArr); @@ -149,11 +152,13 @@ public class ModVisitor extends AbstractVisitor { } } - private static void replaceConstKeys(ClassNode parentClass, SwitchNode insn) { - for (int k = 0; k < insn.getCasesCount(); k++) { - FieldNode f = parentClass.getConstField(insn.getKeys()[k]); + private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { + int[] keys = insn.getKeys(); + int len = keys.length; + for (int k = 0; k < len; k++) { + FieldNode f = parentClass.getConstField(keys[k]); if (f != null) { - insn.getKeys()[k] = f; + insn.modifyKey(k, f); } } } @@ -194,30 +199,30 @@ public class ModVisitor extends AbstractVisitor { if (annotationsList == null) { return; } - for (Annotation annotation : annotationsList.getAll()) { - if (annotation.getVisibility() == Annotation.Visibility.SYSTEM) { + for (IAnnotation annotation : annotationsList.getAll()) { + if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) { continue; } - for (Map.Entry entry : annotation.getValues().entrySet()) { + for (Map.Entry entry : annotation.getValues().entrySet()) { entry.setValue(replaceConstValue(parentCls, entry.getValue())); } } } - @SuppressWarnings({ "unchecked", "rawtypes" }) - private Object replaceConstValue(ClassNode parentCls, @Nullable Object value) { - if (value instanceof List) { - List listVal = (List) value; + @SuppressWarnings("unchecked") + private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) { + if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) { + List listVal = (List) encodedValue.getValue(); if (!listVal.isEmpty()) { listVal.replaceAll(v -> replaceConstValue(parentCls, v)); } - return listVal; + return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal); } - FieldNode constField = parentCls.getConstField(value); + FieldNode constField = parentCls.getConstField(encodedValue.getValue()); if (constField != null) { - return constField.getFieldInfo(); + return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo()); } - return value; + return encodedValue; } private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) { @@ -252,10 +257,10 @@ public class ModVisitor extends AbstractVisitor { } } - private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayNode fillArrInsn) { + private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayInsn fillArrInsn) { int dataSize = fillArrInsn.getSize(); InsnArg arrSizeArg = newArrInsn.getArg(0); - Object value = InsnUtils.getConstValueByArg(mth.dex(), arrSizeArg); + Object value = InsnUtils.getConstValueByArg(mth.root(), arrSizeArg); if (value instanceof LiteralArg) { long literal = ((LiteralArg) value).getLiteral(); return dataSize == (int) literal; @@ -344,7 +349,7 @@ public class ModVisitor extends AbstractVisitor { private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) { MethodInfo callMth = co.getCallMth(); - MethodNode callMthNode = mth.dex().resolveMethod(callMth); + MethodNode callMthNode = mth.root().resolveMethod(callMth); if (callMthNode == null) { return; } @@ -456,7 +461,7 @@ public class ModVisitor extends AbstractVisitor { return parentInsn; } - private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) { + private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayInsn insn) { ArgType insnArrayType = newArrayNode.getArrayType(); ArgType insnElementType = insnArrayType.getArrayElement(); ArgType elType = insn.getElementType(); 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 new file mode 100644 index 000000000..e48fa12a6 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java @@ -0,0 +1,153 @@ +package jadx.core.dex.visitors; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.JumpInfo; +import jadx.core.dex.instructions.BaseInvokeNode; +import jadx.core.dex.instructions.FillArrayData; +import jadx.core.dex.instructions.FillArrayInsn; +import jadx.core.dex.instructions.FilledNewArrayNode; +import jadx.core.dex.instructions.GotoNode; +import jadx.core.dex.instructions.IfNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.SwitchData; +import jadx.core.dex.instructions.SwitchInsn; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.utils.InsnUtils; +import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; + +@JadxVisitor( + name = "Process Instructions Visitor", + desc = "Init instructions info", + runBefore = { + BlockSplitter.class + } +) +public class ProcessInstructionsVisitor extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode()) { + return; + } + + initJumps(mth, mth.getInstructions()); + } + + private static void initJumps(MethodNode mth, InsnNode[] insnByOffset) { + for (int offset = 0; offset < insnByOffset.length; offset++) { + InsnNode insn = insnByOffset[offset]; + if (insn == null) { + continue; + } + switch (insn.getType()) { + case SWITCH: + SwitchInsn sw = (SwitchInsn) insn; + // default case + int nextInsnOffset = getNextInsnOffset(insnByOffset, offset); + if (nextInsnOffset != -1) { + addJump(mth, insnByOffset, offset, nextInsnOffset); + } + 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)); + } + for (int target : sw.getTargets()) { + addJump(mth, insnByOffset, offset, target); + } + break; + + case IF: + int next = getNextInsnOffset(insnByOffset, offset); + if (next != -1) { + addJump(mth, insnByOffset, offset, next); + } + addJump(mth, insnByOffset, offset, ((IfNode) insn).getTarget()); + break; + + case GOTO: + addJump(mth, insnByOffset, offset, ((GotoNode) insn).getTarget()); + break; + + case INVOKE: + ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType(); + mergeMoveResult(insnByOffset, offset, insn, retType); + break; + + case FILLED_NEW_ARRAY: + ArgType arrType = ((FilledNewArrayNode) insn).getArrayType(); + mergeMoveResult(insnByOffset, offset, insn, arrType); + break; + + case FILL_ARRAY: + FillArrayInsn fillArrayInsn = (FillArrayInsn) insn; + int target = fillArrayInsn.getTarget(); + InsnNode arrDataInsn = getInsnAtOffset(insnByOffset, target); + if (arrDataInsn != null && arrDataInsn.getType() == InsnType.FILL_ARRAY_DATA) { + fillArrayInsn.setArrayData((FillArrayData) arrDataInsn); + } else { + throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(target)); + } + break; + + default: + break; + } + } + } + + private static void mergeMoveResult(InsnNode[] insnByOffset, int offset, InsnNode insn, ArgType resType) { + int nextInsnOffset = getNextInsnOffset(insnByOffset, offset); + if (nextInsnOffset == -1) { + return; + } + InsnNode nextInsn = insnByOffset[nextInsnOffset]; + if (nextInsn.getType() != InsnType.MOVE_RESULT) { + return; + } + RegisterArg moveRes = nextInsn.getResult(); + insn.setResult(moveRes.duplicate(resType)); + insn.copyAttributesFrom(nextInsn); + insnByOffset[nextInsnOffset] = null; + } + + private static void addJump(MethodNode mth, InsnNode[] insnByOffset, int offset, int target) { + try { + insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target)); + } catch (Exception e) { + mth.addError("Failed to set jump: " + InsnUtils.formatOffset(offset) + " -> " + InsnUtils.formatOffset(target), e); + } + } + + public static int getNextInsnOffset(InsnNode[] insnByOffset, int offset) { + int len = insnByOffset.length; + for (int i = offset + 1; i < len; i++) { + InsnNode insnNode = insnByOffset[i]; + if (insnNode != null && insnNode.getType() != InsnType.NOP) { + return i; + } + } + return -1; + } + + @Nullable + private static InsnNode getInsnAtOffset(InsnNode[] insnByOffset, int offset) { + int len = insnByOffset.length; + for (int i = offset; i < len; i++) { + InsnNode insnNode = insnByOffset[i]; + if (insnNode != null && insnNode.getType() != InsnType.NOP) { + return insnNode; + } + } + return null; + } +} 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 1cf107ca2..961d4daf0 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 @@ -17,7 +17,7 @@ import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.NewArrayNode; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -26,10 +26,10 @@ 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.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.InsnList; import jadx.core.utils.InsnRemover; @@ -80,7 +80,7 @@ public class ReSugarCode extends AbstractVisitor { break; case SWITCH: - processEnumSwitch(mth, (SwitchNode) insn); + processEnumSwitch(mth, (SwitchInsn) insn); break; default: @@ -156,7 +156,7 @@ public class ReSugarCode extends AbstractVisitor { return false; } InsnArg indexArg = insn.getArg(1); - Object value = InsnUtils.getConstValueByArg(mth.dex(), indexArg); + Object value = InsnUtils.getConstValueByArg(mth.root(), indexArg); if (value instanceof LiteralArg) { int index = (int) ((LiteralArg) value).getLiteral(); return index == putIndex; @@ -164,7 +164,7 @@ public class ReSugarCode extends AbstractVisitor { return false; } - private static void processEnumSwitch(MethodNode mth, SwitchNode insn) { + private static void processEnumSwitch(MethodNode mth, SwitchInsn insn) { InsnArg arg = insn.getArg(0); if (!arg.isInsnWrap()) { return; @@ -173,7 +173,7 @@ public class ReSugarCode extends AbstractVisitor { if (wrapInsn.getType() != InsnType.AGET) { return; } - EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.dex(), wrapInsn); + EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.root(), wrapInsn); if (enumMapInfo == null) { return; } @@ -184,8 +184,9 @@ public class ReSugarCode extends AbstractVisitor { if (valueMap == null) { return; } - Object[] keys = insn.getKeys(); - for (Object key : keys) { + int caseCount = insn.getKeys().length; + for (int i = 0; i < caseCount; i++) { + Object key = insn.getKey(i); Object newKey = valueMap.get(key); if (newKey == null) { return; @@ -195,8 +196,8 @@ public class ReSugarCode extends AbstractVisitor { if (!insn.replaceArg(arg, invArg)) { return; } - for (int i = 0; i < keys.length; i++) { - keys[i] = valueMap.get(keys[i]); + for (int i = 0; i < caseCount; i++) { + insn.modifyKey(i, valueMap.get(insn.getKey(i))); } enumMapField.add(AFlag.DONT_GENERATE); checkAndHideClass(enumMapField.getParentClass()); @@ -211,7 +212,7 @@ public class ReSugarCode extends AbstractVisitor { for (BlockNode block : clsInitMth.getBasicBlocks()) { for (InsnNode insn : block.getInstructions()) { if (insn.getType() == InsnType.APUT) { - addToEnumMap(enumCls.dex(), mapAttr, insn); + addToEnumMap(enumCls.root(), mapAttr, insn); } } } @@ -230,12 +231,12 @@ public class ReSugarCode extends AbstractVisitor { return mapAttr.getMap(field); } - private static void addToEnumMap(DexNode dex, EnumMapAttr mapAttr, InsnNode aputInsn) { + private static void addToEnumMap(RootNode root, EnumMapAttr mapAttr, InsnNode aputInsn) { InsnArg litArg = aputInsn.getArg(2); if (!litArg.isLiteral()) { return; } - EnumMapInfo mapInfo = checkEnumMapAccess(dex, aputInsn); + EnumMapInfo mapInfo = checkEnumMapAccess(root, aputInsn); if (mapInfo == null) { return; } @@ -252,7 +253,7 @@ public class ReSugarCode extends AbstractVisitor { if (!(index instanceof FieldInfo)) { return; } - FieldNode fieldNode = dex.resolveField((FieldInfo) index); + FieldNode fieldNode = root.resolveField((FieldInfo) index); if (fieldNode == null) { return; } @@ -260,7 +261,7 @@ public class ReSugarCode extends AbstractVisitor { mapAttr.add(field, literal, fieldNode); } - public static EnumMapInfo checkEnumMapAccess(DexNode dex, InsnNode checkInsn) { + public static EnumMapInfo checkEnumMapAccess(RootNode root, InsnNode checkInsn) { InsnArg sgetArg = checkInsn.getArg(0); InsnArg invArg = checkInsn.getArg(1); if (!sgetArg.isInsnWrap() || !invArg.isInsnWrap()) { @@ -275,7 +276,7 @@ public class ReSugarCode extends AbstractVisitor { if (!inv.getCallMth().getShortId().equals("ordinal()I")) { return null; } - ClassNode enumCls = dex.resolveClass(inv.getCallMth().getDeclClass()); + ClassNode enumCls = root.resolveClass(inv.getCallMth().getDeclClass()); if (enumCls == null || !enumCls.isEnum()) { return null; } @@ -283,7 +284,7 @@ public class ReSugarCode extends AbstractVisitor { if (!(index instanceof FieldInfo)) { return null; } - FieldNode enumMapField = dex.resolveField((FieldInfo) index); + FieldNode enumMapField = root.resolveField((FieldInfo) index); if (enumMapField == null || !enumMapField.getAccessFlags().isSynthetic()) { return null; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 188504636..8ca47b186 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -1,5 +1,6 @@ package jadx.core.dex.visitors; +import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -19,28 +20,25 @@ import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.InputFile; public class RenameVisitor extends AbstractVisitor { @Override public void init(RootNode root) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { + List inputFiles = root.getArgs().getInputFiles(); + if (inputFiles.isEmpty()) { return; } - InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile(); - Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath(); + Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath(); String baseName = FileUtils.getPathBaseName(inputFilePath); Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf"); JadxArgs args = root.getArgs(); - Deobfuscator deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath); + Deobfuscator deobfuscator = new Deobfuscator(args, root, deobfMapPath); if (args.isDeobfuscationOn()) { deobfuscator.execute(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index 561e92cd3..f893018f3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -9,6 +9,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.plugins.input.data.ILocalVar; import jadx.core.Consts; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -111,15 +112,16 @@ public class DebugInfoApplyVisitor extends AbstractVisitor { int startOffset = getInsnOffsetByArg(ssaVar.getAssign()); int endOffset = max.get(); int regNum = ssaVar.getRegNum(); - for (LocalVar localVar : debugInfoAttr.getLocalVars()) { + for (ILocalVar localVar : debugInfoAttr.getLocalVars()) { if (localVar.getRegNum() == regNum) { - int startAddr = localVar.getStartAddr(); - int endAddr = localVar.getEndAddr(); + int startAddr = localVar.getStartOffset(); + int endAddr = localVar.getEndOffset(); if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) { if (Consts.DEBUG) { LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar); } - applyDebugInfo(mth, ssaVar, localVar.getType(), localVar.getName()); + ArgType type = DebugInfoAttachVisitor.getVarType(localVar); + applyDebugInfo(mth, ssaVar, type, localVar.getName()); break; } } 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 new file mode 100644 index 000000000..a04c1a4f8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java @@ -0,0 +1,163 @@ +package jadx.core.dex.visitors.debuginfo; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ILocalVar; +import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; +import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; +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; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.parser.SignatureParser; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.dex.visitors.ssa.SSATransform; +import jadx.core.utils.ErrorsCounter; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxException; + +import static jadx.core.codegen.CodeWriter.NL; + +@JadxVisitor( + name = "Debug Info Parser", + desc = "Attach debug information (variable names and types, instruction lines)", + runBefore = { + BlockSplitter.class, + SSATransform.class + } +) +public class DebugInfoAttachVisitor extends AbstractVisitor { + + private static final Logger LOG = LoggerFactory.getLogger(DebugInfoAttachVisitor.class); + + @Override + public void visit(MethodNode mth) throws JadxException { + try { + IDebugInfo debugInfo = mth.getDebugInfo(); + if (debugInfo != null) { + processDebugInfo(mth, debugInfo); + } + } catch (Exception e) { + mth.addComment("JADX WARNING: Error to parse debug info: " + + ErrorsCounter.formatMsg(mth, e.getMessage()) + + NL + Utils.getStackTrace(e)); + } + } + + private void processDebugInfo(MethodNode mth, IDebugInfo debugInfo) { + InsnNode[] insnArr = mth.getInstructions(); + attachSourceLines(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 attachDebugInfo(MethodNode mth, List localVars, InsnNode[] insnArr) { + if (localVars.isEmpty()) { + return; + } + for (ILocalVar var : localVars) { + int regNum = var.getRegNum(); + int start = var.getStartOffset(); + int end = var.getEndOffset(); + + ArgType type = getVarType(var); + RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(type, var.getName()); + if (start < 0) { + // attach to method arguments + RegisterArg thisArg = mth.getThisArg(); + if (thisArg != null) { + attachDebugInfo(thisArg, debugInfoAttr, regNum); + } + for (RegisterArg arg : mth.getArgRegs()) { + attachDebugInfo(arg, debugInfoAttr, regNum); + } + start = 0; + } + for (int i = start; i <= end; i++) { + InsnNode insn = insnArr[i]; + if (insn != null) { + attachDebugInfo(insn.getResult(), debugInfoAttr, regNum); + for (InsnArg arg : insn.getArguments()) { + attachDebugInfo(arg, debugInfoAttr, regNum); + } + } + } + } + + mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); + } + + private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) { + if (arg instanceof RegisterArg) { + RegisterArg reg = (RegisterArg) arg; + if (regNum == reg.getRegNum()) { + reg.addAttr(debugInfoAttr); + } + } + } + + public static ArgType getVarType(ILocalVar var) { + ArgType type = ArgType.parse(var.getType()); + String sign = var.getSignature(); + if (sign == null) { + return type; + } + try { + ArgType gType = new SignatureParser(sign).consumeType(); + if (checkSignature(type, gType)) { + return gType; + } + } catch (Exception e) { + LOG.error("Can't parse signature for local variable: {}", sign, e); + } + return type; + } + + private static boolean checkSignature(ArgType type, ArgType gType) { + boolean apply; + ArgType el = gType.getArrayRootElement(); + if (el.isGeneric()) { + if (!type.getArrayRootElement().getObject().equals(el.getObject())) { + LOG.warn("Generic type in debug info not equals: {} != {}", type, gType); + } + apply = true; + } else { + apply = el.isGenericType(); + } + return apply; + } + + /** + * Set method source line from first instruction + */ + private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) { + for (InsnNode insn : insnArr) { + if (insn != null) { + int line = insn.getSourceLine(); + if (line != 0) { + mth.setSourceLine(line - 1); + return; + } + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java deleted file mode 100644 index 185ab03a3..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java +++ /dev/null @@ -1,119 +0,0 @@ -package jadx.core.dex.visitors.debuginfo; - -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.Consts; -import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; -import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.visitors.AbstractVisitor; -import jadx.core.dex.visitors.JadxVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; -import jadx.core.dex.visitors.ssa.SSATransform; -import jadx.core.utils.ErrorsCounter; -import jadx.core.utils.Utils; -import jadx.core.utils.exceptions.JadxException; - -import static jadx.core.codegen.CodeWriter.NL; - -@JadxVisitor( - name = "Debug Info Parser", - desc = "Parse debug information (variable names and types, instruction lines)", - runBefore = { - BlockSplitter.class, - SSATransform.class - } -) -public class DebugInfoParseVisitor extends AbstractVisitor { - - private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParseVisitor.class); - - @Override - public void visit(MethodNode mth) throws JadxException { - try { - int debugOffset = mth.getDebugInfoOffset(); - if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) { - processDebugInfo(mth, debugOffset); - } - } catch (Exception e) { - mth.addComment("JADX WARNING: Error to parse debug info: " - + ErrorsCounter.formatMsg(mth, e.getMessage()) - + NL + Utils.getStackTrace(e)); - } - } - - private void processDebugInfo(MethodNode mth, int debugOffset) { - InsnNode[] insnArr = mth.getInstructions(); - DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); - List localVars = debugInfoParser.process(); - attachDebugInfo(mth, localVars, insnArr); - setMethodSourceLine(mth, insnArr); - } - - private void attachDebugInfo(MethodNode mth, List localVars, InsnNode[] insnArr) { - if (localVars.isEmpty()) { - return; - } - if (Consts.DEBUG) { - LOG.debug("Parsed debug info for {}: ", mth); - localVars.forEach(v -> LOG.debug(" {}", v)); - } - localVars.forEach(var -> { - int start = var.getStartAddr(); - int end = var.getEndAddr(); - RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var); - if (start < 0) { - // attach to method arguments - RegisterArg thisArg = mth.getThisArg(); - if (thisArg != null) { - attachDebugInfo(thisArg, var, debugInfoAttr); - } - for (RegisterArg arg : mth.getArgRegs()) { - attachDebugInfo(arg, var, debugInfoAttr); - } - start = 0; - } - for (int i = start; i <= end; i++) { - InsnNode insn = insnArr[i]; - if (insn != null) { - attachDebugInfo(insn.getResult(), var, debugInfoAttr); - for (InsnArg arg : insn.getArguments()) { - attachDebugInfo(arg, var, debugInfoAttr); - } - } - } - }); - - mth.addAttr(new LocalVarsDebugInfoAttr(localVars)); - } - - private void attachDebugInfo(InsnArg arg, LocalVar var, RegDebugInfoAttr debugInfoAttr) { - if (arg instanceof RegisterArg) { - RegisterArg reg = (RegisterArg) arg; - if (var.getRegNum() == reg.getRegNum()) { - reg.addAttr(debugInfoAttr); - } - } - } - - /** - * Set method source line from first instruction - */ - private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) { - for (InsnNode insn : insnArr) { - if (insn != null) { - int line = insn.getSourceLine(); - if (line != 0) { - mth.setSourceLine(line - 1); - return; - } - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java deleted file mode 100644 index 35a5fa122..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/LocalVar.java +++ /dev/null @@ -1,120 +0,0 @@ -package jadx.core.dex.visitors.debuginfo; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.parser.SignatureParser; -import jadx.core.utils.InsnUtils; - -public final class LocalVar { - private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class); - - private final int regNum; - private final String name; - private final ArgType type; - - private boolean isEnd; - private int startAddr; - private int endAddr; - - public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) { - this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId)); - } - - public LocalVar(int regNum, String name, ArgType type) { - this(regNum, name, type, null); - } - - public LocalVar(int regNum, String name, ArgType type, String sign) { - this.regNum = regNum; - this.name = name; - if (sign != null) { - try { - ArgType gType = new SignatureParser(sign).consumeType(); - if (checkSignature(type, gType)) { - type = gType; - } - } catch (Exception e) { - LOG.error("Can't parse signature for local variable: {}", sign, e); - } - } - this.type = type; - } - - private boolean checkSignature(ArgType type, ArgType gType) { - boolean apply; - ArgType el = gType.getArrayRootElement(); - if (el.isGeneric()) { - if (!type.getArrayRootElement().getObject().equals(el.getObject())) { - LOG.warn("Generic type in debug info not equals: {} != {}", type, gType); - } - apply = true; - } else { - apply = el.isGenericType(); - } - return apply; - } - - public void start(int addr) { - this.isEnd = false; - this.startAddr = addr; - } - - /** - * Sets end address of local variable - * - * @param addr address - * @return true if local variable was active, else false - */ - public boolean end(int addr) { - if (isEnd) { - return false; - } - this.isEnd = true; - this.endAddr = addr; - return true; - } - - public int getRegNum() { - return regNum; - } - - public String getName() { - return name; - } - - public ArgType getType() { - return type; - } - - public boolean isEnd() { - return isEnd; - } - - public int getStartAddr() { - return startAddr; - } - - public int getEndAddr() { - return endAddr; - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public String toString() { - return InsnUtils.formatOffset(startAddr) - + '-' + (isEnd ? InsnUtils.formatOffset(endAddr) : " ") - + ": r" + regNum + " '" + name + "' " + type; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index 642ca5f5f..8171f5000 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -61,7 +61,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { // for each try block search nearest dominator block for (TryCatchBlock tb : tryBlocks) { if (tb.getHandlersCount() == 0) { - mth.addWarn("No exception handlers in catch block: " + tb); + // mth.addWarn("No exception handlers in catch block: " + tb); continue; } processTryCatchBlock(mth, tb, tryBlocksMap); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java index d538ce714..ec2d88b7c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java @@ -22,7 +22,7 @@ import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.SwitchNode; +import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.Edge; @@ -127,7 +127,7 @@ public class RegionMaker { break; case SWITCH: - next = processSwitch(r, block, (SwitchNode) insn, stack); + next = processSwitch(r, block, (SwitchInsn) insn, stack); processed = true; break; @@ -738,15 +738,14 @@ public class RegionMaker { region.add(start); } - private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) { + private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchInsn insn, RegionStack stack) { // map case blocks to keys int len = insn.getTargets().length; Map> blocksMap = new LinkedHashMap<>(len); - Object[] keysArr = insn.getKeys(); BlockNode[] targetBlocksArr = insn.getTargetBlocks(); for (int i = 0; i < len; i++) { List keys = blocksMap.computeIfAbsent(targetBlocksArr[i], k -> new ArrayList<>(2)); - keys.add(keysArr[i]); + keys.add(insn.getKey(i)); } BlockNode defCase = insn.getDefTargetBlock(); if (defCase != null) { @@ -899,7 +898,7 @@ public class RegionMaker { * 1. single 'default' case * 2. filler cases if switch is 'packed' and 'default' case is empty */ - private void removeEmptyCases(SwitchNode insn, SwitchRegion sw, BlockNode defCase) { + private void removeEmptyCases(SwitchInsn insn, SwitchRegion sw, BlockNode defCase) { boolean defaultCaseIsEmpty; if (defCase == null) { defaultCaseIsEmpty = true; diff --git a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java index 8b5d4e0a6..bc8b86ab5 100644 --- a/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java +++ b/jadx-core/src/main/java/jadx/core/export/ExportGradleProject.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; import org.slf4j.Logger; @@ -12,7 +11,6 @@ import org.slf4j.LoggerFactory; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; @@ -62,14 +60,11 @@ public class ExportGradleProject { } private void skipGeneratedClasses() { - for (DexNode dexNode : root.getDexNodes()) { - List classes = dexNode.getClasses(); - for (ClassNode cls : classes) { - String shortName = cls.getClassInfo().getShortName(); - if (IGNORE_CLS_NAMES.contains(shortName)) { - cls.add(AFlag.DONT_GENERATE); - LOG.debug("Skip class: {}", cls); - } + for (ClassNode cls : root.getClasses()) { + String shortName = cls.getClassInfo().getShortName(); + if (IGNORE_CLS_NAMES.contains(shortName)) { + cls.add(AFlag.DONT_GENERATE); + LOG.debug("Skip class: {}", cls); } } } diff --git a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java b/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java deleted file mode 100644 index 32c67240b..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/AsmUtils.java +++ /dev/null @@ -1,22 +0,0 @@ -package jadx.core.utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -import org.objectweb.asm.ClassReader; - -public class AsmUtils { - - private AsmUtils() { - } - - public static String getNameFromClassFile(File file) throws IOException { - String className; - try (FileInputStream in = new FileInputStream(file)) { - ClassReader classReader = new ClassReader(in); - className = classReader.getClassName(); - } - return className; - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java index c886a42ef..dd06b64cf 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java +++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java @@ -1,5 +1,6 @@ package jadx.core.utils; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -36,7 +37,12 @@ public class ErrorsCounter { } public static String formatMsg(IDexNode node, String msg) { - return msg + " in " + node.typeName() + ": " + node + ", dex: " + node.dex().getDexFile().getName(); + return msg + " in " + node.typeName() + ": " + node + ", file: " + getNodeFile(node); + } + + private static String getNodeFile(IDexNode node) { + Path inputPath = node.getInputPath(); + return inputPath == null ? "synthetic" : inputPath.toString(); } private synchronized String addError(N node, String error, @Nullable Throwable e) { 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 d76c86779..c18ebc50c 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -6,10 +6,9 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.io.instructions.DecodedInstruction; - import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstStringNode; @@ -20,12 +19,10 @@ import jadx.core.dex.instructions.args.InsnWrapArg; 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.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; -import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.dex.nodes.RootNode; public class InsnUtils { @@ -34,23 +31,6 @@ public class InsnUtils { private InsnUtils() { } - public static int getArg(DecodedInstruction insn, int arg) { - switch (arg) { - case 0: - return insn.getA(); - case 1: - return insn.getB(); - case 2: - return insn.getC(); - case 3: - return insn.getD(); - case 4: - return insn.getE(); - default: - throw new JadxRuntimeException("Wrong argument number: " + arg); - } - } - public static String formatOffset(int offset) { if (offset < 0) { return "?"; @@ -77,7 +57,7 @@ public class InsnUtils { * * @return LiteralArg, String, ArgType or null */ - public static Object getConstValueByArg(DexNode dex, InsnArg arg) { + public static Object getConstValueByArg(RootNode root, InsnArg arg) { if (arg.isLiteral()) { return arg; } @@ -88,13 +68,13 @@ public class InsnUtils { return null; } if (parInsn.getType() == InsnType.MOVE) { - return getConstValueByArg(dex, parInsn.getArg(0)); + return getConstValueByArg(root, parInsn.getArg(0)); } - return getConstValueByInsn(dex, parInsn); + return getConstValueByInsn(root, parInsn); } if (arg.isInsnWrap()) { InsnNode insn = ((InsnWrapArg) arg).getWrapInsn(); - return getConstValueByInsn(dex, insn); + return getConstValueByInsn(root, insn); } return null; } @@ -105,7 +85,7 @@ public class InsnUtils { * @return LiteralArg, String, ArgType or null */ @Nullable - public static Object getConstValueByInsn(DexNode dex, InsnNode insn) { + public static Object getConstValueByInsn(RootNode root, InsnNode insn) { switch (insn.getType()) { case CONST: return insn.getArg(0); @@ -115,13 +95,19 @@ public class InsnUtils { return ((ConstClassNode) insn).getClsType(); case SGET: FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex(); - FieldNode fieldNode = dex.root().deepResolveField(f); + FieldNode fieldNode = root.deepResolveField(f); if (fieldNode == null) { - LOG.warn("Field {} not found in dex {}", f, dex); + LOG.warn("Field {} not found", f); return null; } FieldInitAttr attr = fieldNode.get(AType.FIELD_INIT); - return attr != null ? attr.getValue() : null; + if (attr != null) { + if (attr.getValueType() == FieldInitAttr.InitType.CONST) { + return attr.getEncodedValue().getValue(); + } + return attr.getInsn(); + } + return null; default: return null; diff --git a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java index 6ac93d7e4..1ff8f2bdf 100644 --- a/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/SmaliUtils.java @@ -1,12 +1,14 @@ package jadx.core.utils; +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.StringWriter; +import java.nio.file.Files; import java.nio.file.Path; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.baksmali.BaksmaliOptions; -import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.dexbacked.DexBackedClassDef; import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.smali.Smali; @@ -15,7 +17,6 @@ import org.jf.util.IndentingWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.core.dex.nodes.DexNode; import jadx.core.utils.exceptions.JadxRuntimeException; // TODO: move smali dependencies out from jadx-core @@ -33,10 +34,12 @@ public class SmaliUtils { } } - public static boolean getSmaliCode(DexNode dex, int clsDefOffset, StringWriter stringWriter) { - try { - Path path = dex.getDexFile().getPath(); - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null); + public static boolean getSmaliCode(Path path, int clsDefOffset, StringWriter stringWriter) { + if (clsDefOffset == 0) { + return false; + } + try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) { + DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(null, inputStream); DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0); getSmaliCode(dexBackedClassDef, stringWriter); return true; 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 334219007..d59accfb8 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 @@ -10,24 +10,24 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.android.dx.rop.code.AccessFlags; - +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.core.codegen.ClassGen; import jadx.core.codegen.CodeWriter; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ConstStorage; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.ProcessState; import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.entry.ResourceEntry; @@ -44,7 +44,7 @@ public class AndroidResourcesUtils { public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) { String appPackage = root.getAppPackage(); String fullName = appPackage != null ? appPackage + ".R" : "R"; - ClassNode resCls = root.searchClassByName(fullName); + ClassNode resCls = root.resolveClass(fullName); if (resCls != null) { addResourceFields(resCls, resStorage, true); return resCls; @@ -82,11 +82,7 @@ public class AndroidResourcesUtils { @Nullable private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { - return null; - } - ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL); + ClassNode rCls = new ClassNode(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); rCls.addAttr(AType.COMMENTS, "This class is generated by JADX"); rCls.setState(ProcessState.PROCESS_COMPLETE); return rCls; @@ -113,9 +109,10 @@ public class AndroidResourcesUtils { } FieldNode rField = typeCls.searchFieldByName(resName); if (rField == null) { - FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT); - rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); - rField.addAttr(FieldInitAttr.constValue(resource.getId())); + 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)); typeCls.getFields().add(rField); if (rClsExists) { rField.addAttr(AType.COMMENTS, "added by JADX"); @@ -134,8 +131,8 @@ public class AndroidResourcesUtils { @NotNull private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { - ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + '$' + typeName, - AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); + ClassNode newTypeCls = new ClassNode(resCls.root(), resCls.getFullName() + '$' + typeName, + AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); resCls.addInnerClass(newTypeCls); if (rClsExists) { newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); diff --git a/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java b/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java deleted file mode 100644 index 1b490f427..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/DexFile.java +++ /dev/null @@ -1,40 +0,0 @@ -package jadx.core.utils.files; - -import java.nio.file.Path; - -import com.android.dex.Dex; - -public class DexFile { - private final InputFile inputFile; - private final String name; - private final Dex dexBuf; - private final Path path; - - public DexFile(InputFile inputFile, String name, Dex dexBuf, Path path) { - this.inputFile = inputFile; - this.name = name; - this.dexBuf = dexBuf; - this.path = path; - } - - public String getName() { - return name; - } - - public Dex getDexBuf() { - return dexBuf; - } - - public Path getPath() { - return path; - } - - public InputFile getInputFile() { - return inputFile; - } - - @Override - public String toString() { - return inputFile + (name.isEmpty() ? "" : ':' + name); - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java deleted file mode 100644 index 27c3bf188..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ /dev/null @@ -1,233 +0,0 @@ -package jadx.core.utils.files; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarOutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.android.dex.Dex; -import com.android.dex.DexException; - -import jadx.core.utils.AsmUtils; -import jadx.core.utils.SmaliUtils; -import jadx.core.utils.exceptions.DecodeException; -import jadx.core.utils.exceptions.JadxException; -import jadx.core.utils.exceptions.JadxRuntimeException; - -import static jadx.core.codegen.CodeWriter.NL; -import static jadx.core.utils.files.FileUtils.isApkFile; -import static jadx.core.utils.files.FileUtils.isZipDexFile; - -public class InputFile { - private static final Logger LOG = LoggerFactory.getLogger(InputFile.class); - - private final File file; - private final List dexFiles = new ArrayList<>(); - - public static void addFilesFrom(File file, List list, boolean skipSources) throws IOException, DecodeException { - InputFile inputFile = new InputFile(file); - inputFile.searchDexFiles(skipSources); - list.add(inputFile); - } - - private InputFile(File file) throws IOException { - if (!file.exists()) { - throw new IOException("File not found: " + file.getAbsolutePath()); - } - this.file = file; - } - - private void searchDexFiles(boolean skipSources) throws IOException, DecodeException { - String fileName = file.getName(); - - if (fileName.endsWith(".dex")) { - addDexFile(fileName, file.toPath()); - return; - } - if (fileName.endsWith(".smali")) { - Path output = FileUtils.createTempFile(".dex"); - SmaliUtils.assembleDex(output.toAbsolutePath().toString(), file.getAbsolutePath()); - addDexFile(fileName, output); - return; - } - if (fileName.endsWith(".class")) { - for (Path path : loadFromClassFile(file)) { - addDexFile(fileName, path); - } - return; - } - if (isApkFile(file) || isZipDexFile(file)) { - loadFromZip(".dex"); - return; - } - if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) { - // check if jar/aar contains '.dex' files - if (loadFromZip(".dex")) { - return; - } - if (fileName.endsWith(".jar")) { - for (Path path : loadFromJar(file.toPath())) { - addDexFile(fileName, path); - } - return; - } - if (fileName.endsWith(".aar")) { - loadFromZip(".jar"); - return; - } - return; - } - if (skipSources) { - return; - } - LOG.warn("No dex files found in {}", file); - } - - private boolean loadFromZip(String ext) throws IOException, DecodeException { - int index = 0; - try (ZipFile zf = new ZipFile(file)) { - // Input file could be .apk or .zip files - // we should consider the input file could contain only one single dex, multi-dex, - // or instantRun support dex for Android .apk files - String instantRunDexSuffix = "classes" + ext; - for (Enumeration e = zf.entries(); e.hasMoreElements();) { - ZipEntry entry = e.nextElement(); - if (!ZipSecurity.isValidZipEntry(entry)) { - continue; - } - - String entryName = entry.getName(); - try (InputStream inputStream = zf.getInputStream(entry)) { - if ((entryName.startsWith("classes") && entryName.endsWith(ext)) - || entryName.endsWith(instantRunDexSuffix)) { - switch (ext) { - case ".dex": - Path path = copyToTmpDex(entryName, inputStream); - if (addDexFile(entryName, path)) { - index++; - } - break; - - case ".jar": - index++; - Path jarFile = FileUtils.createTempFile(entryName); - Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING); - for (Path p : loadFromJar(jarFile)) { - addDexFile(entryName, p); - } - break; - - default: - throw new JadxRuntimeException("Unexpected extension in zip: " + ext); - } - } else if (entryName.equals("instant-run.zip") && ext.equals(".dex")) { - Path jarFile = FileUtils.createTempFile("instant-run.zip"); - Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING); - InputFile tempFile = new InputFile(jarFile.toFile()); - tempFile.loadFromZip(ext); - List files = tempFile.getDexFiles(); - if (!files.isEmpty()) { - index += files.size(); - this.dexFiles.addAll(files); - } - } - } - } - } - return index > 0; - } - - private boolean addDexFile(String entryName, @Nullable Path filePath) { - if (filePath == null) { - return false; - } - Dex dexBuf = loadDexBufFromPath(filePath, entryName); - if (dexBuf == null) { - return false; - } - dexFiles.add(new DexFile(this, entryName, dexBuf, filePath)); - return true; - } - - @Nullable - private Dex loadDexBufFromPath(Path path, String entryName) { - try { - return new Dex(Files.readAllBytes(path)); - } catch (DexException e) { - LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage()); - } catch (Exception e) { - LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage(), e); - } - return null; - } - - @Nullable - private Path copyToTmpDex(String entryName, InputStream inputStream) { - try { - Path path = FileUtils.createTempFile(".dex"); - Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING); - return path; - } catch (Exception e) { - LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e); - return null; - } - } - - private static List loadFromJar(Path jar) throws DecodeException { - JavaToDex j2d = new JavaToDex(); - try { - LOG.info("converting to dex: {} ...", jar.getFileName()); - List pathList = j2d.convert(jar); - if (pathList.isEmpty()) { - throw new JadxException("Empty dx output"); - } - if (LOG.isDebugEnabled()) { - LOG.debug("result dex files: {}", pathList); - } - return pathList; - } catch (Exception e) { - throw new DecodeException("java class to dex conversion error:" + NL + " " + e.getMessage(), e); - } finally { - if (j2d.isError()) { - LOG.warn("dx message: {}", j2d.getDxErrors()); - } - } - } - - private static List loadFromClassFile(File file) throws IOException, DecodeException { - Path outFile = FileUtils.createTempFile(".jar"); - try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) { - String clsName = AsmUtils.getNameFromClassFile(file); - if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) { - throw new IOException("Can't read class name from file: " + file); - } - FileUtils.addFileToJar(jo, file, clsName + ".class"); - } - return loadFromJar(outFile); - } - - public File getFile() { - return file; - } - - public List getDexFiles() { - return dexFiles; - } - - @Override - public String toString() { - return file.getAbsolutePath(); - } -} diff --git a/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java b/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java deleted file mode 100644 index ec37aebaa..000000000 --- a/jadx-core/src/main/java/jadx/core/utils/files/JavaToDex.java +++ /dev/null @@ -1,74 +0,0 @@ -package jadx.core.utils.files; - -import java.io.ByteArrayOutputStream; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import com.android.dx.command.dexer.DxContext; -import com.android.dx.command.dexer.Main; -import com.android.dx.command.dexer.Main.Arguments; - -import jadx.core.utils.exceptions.JadxException; - -public class JavaToDex { - - private static final String CHARSET_NAME = "UTF-8"; - - private static class DxArgs extends Arguments { - public DxArgs(DxContext context, String dexDir, String[] input) { - super(context); - outName = dexDir; - fileNames = input; - jarOutput = false; - multiDex = true; - - optimize = true; - localInfo = true; - coreLibrary = true; - - debug = true; - warnings = true; - minSdkVersion = 28; - } - } - - private String dxErrors; - - public List convert(Path jar) throws JadxException { - try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - ByteArrayOutputStream errOut = new ByteArrayOutputStream()) { - DxContext context = new DxContext(out, errOut); - Path dir = FileUtils.createTempDir("jar-to-dex-"); - DxArgs args = new DxArgs( - context, - dir.toAbsolutePath().toString(), - new String[] { jar.toAbsolutePath().toString() }); - int result = new Main(context).runDx(args); - dxErrors = errOut.toString(CHARSET_NAME); - if (result != 0) { - throw new JadxException("Java to dex conversion error, code: " + result); - } - List list = new ArrayList<>(); - try (DirectoryStream ds = Files.newDirectoryStream(dir)) { - for (Path child : ds) { - list.add(child); - child.toFile().deleteOnExit(); - } - } - return list; - } catch (Exception e) { - throw new JadxException("dx exception: " + e.getMessage(), e); - } - } - - public String getDxErrors() { - return dxErrors; - } - - public boolean isError() { - return dxErrors != null && !dxErrors.isEmpty(); - } -} diff --git a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java index 805f7123c..49c07bdbb 100644 --- a/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java +++ b/jadx-core/src/test/java/jadx/api/JadxDecompilerTest.java @@ -1,20 +1,21 @@ package jadx.api; import java.io.File; +import java.net.URL; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import jadx.core.utils.files.FileUtils; -import jadx.core.utils.files.InputFileTest; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; public class JadxDecompilerTest { @Test public void testExampleUsage() { - File sampleApk = InputFileTest.getFileFromSampleDir("app-with-fake-dex.apk"); + File sampleApk = getFileFromSampleDir("app-with-fake-dex.apk"); File outDir = FileUtils.createTempDir("jadx-usage-example").toFile(); // test simple apk loading @@ -22,18 +23,28 @@ public class JadxDecompilerTest { args.getInputFiles().add(sampleApk); args.setOutDir(outDir); - JadxDecompiler jadx = new JadxDecompiler(args); - jadx.load(); - jadx.save(); - jadx.printErrorsReport(); + try (JadxDecompiler jadx = new JadxDecompiler(args)) { + jadx.load(); + jadx.save(); + jadx.printErrorsReport(); - // test class print - for (JavaClass cls : jadx.getClasses()) { - System.out.println(cls.getCode()); + // test class print + for (JavaClass cls : jadx.getClasses()) { + System.out.println(cls.getCode()); + } + + assertThat(jadx.getClasses(), Matchers.hasSize(3)); + assertThat(jadx.getErrorsCount(), Matchers.is(0)); } + } - assertThat(jadx.getClasses(), Matchers.hasSize(3)); - assertThat(jadx.getErrorsCount(), Matchers.is(0)); + private static final String TEST_SAMPLES_DIR = "test-samples/"; + + public static File getFileFromSampleDir(String fileName) { + URL resource = JadxDecompilerTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName); + assertThat(resource, notNullValue()); + String pathStr = resource.getFile(); + return new File(pathStr); } // TODO add more tests diff --git a/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java b/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java index 23587c044..f65b92a9c 100644 --- a/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/info/AccessInfoTest.java @@ -2,8 +2,7 @@ package jadx.core.dex.info; import org.junit.jupiter.api.Test; -import com.android.dx.rop.code.AccessFlags; - +import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.info.AccessInfo.AFType; import static org.hamcrest.MatcherAssert.assertThat; @@ -14,8 +13,8 @@ public class AccessInfoTest { @Test public void changeVisibility() { - AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PROTECTED | AccessFlags.ACC_STATIC, AFType.METHOD); - AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC); + AccessInfo accessInfo = new AccessInfo(AccessFlags.PROTECTED | AccessFlags.STATIC, AFType.METHOD); + AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC); assertThat(result.isPublic(), is(true)); assertThat(result.isPrivate(), is(false)); @@ -26,8 +25,8 @@ public class AccessInfoTest { @Test public void changeVisibilityNoOp() { - AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PUBLIC, AFType.METHOD); - AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC); + AccessInfo accessInfo = new AccessInfo(AccessFlags.PUBLIC, AFType.METHOD); + AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC); assertSame(accessInfo, result); } } diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java index 035a35892..220f30c61 100644 --- a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -43,7 +43,7 @@ public class TypeCompareTest { public void init() { JadxArgs args = new JadxArgs(); RootNode root = new RootNode(args); - root.load(Collections.emptyList()); + root.loadClasses(Collections.emptyList()); root.initClassPath(); compare = new TypeCompare(root); } diff --git a/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java b/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java deleted file mode 100644 index f86c5d7bf..000000000 --- a/jadx-core/src/test/java/jadx/core/utils/files/InputFileTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package jadx.core.utils.files; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import jadx.core.utils.exceptions.DecodeException; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.notNullValue; - -public class InputFileTest { - private static final String TEST_SAMPLES_DIR = "test-samples/"; - - @Test - public void testApkWithFakeDex() throws IOException, DecodeException { - File sample = getFileFromSampleDir("app-with-fake-dex.apk"); - - List list = new ArrayList<>(); - InputFile.addFilesFrom(sample, list, false); - assertThat(list, hasSize(1)); - InputFile inputFile = list.get(0); - assertThat(inputFile.getDexFiles(), hasSize(1)); - } - - public static File getFileFromSampleDir(String fileName) { - URL resource = InputFileTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName); - assertThat(resource, notNullValue()); - String pathStr = resource.getFile(); - return new File(pathStr); - } -} 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 2e4bec5fb..c27a1d6fd 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -104,6 +104,8 @@ public abstract class IntegrationTest extends TestUtils { DebugChecks.checksEnabled = true; } + private JadxDecompiler jadxDecompiler; + @BeforeEach public void init() { this.deleteTmpFiles = true; @@ -124,6 +126,9 @@ public abstract class IntegrationTest extends TestUtils { @AfterEach public void after() { FileUtils.clearTempRootDir(); + if (jadxDecompiler != null) { + jadxDecompiler.close(); + } } public String getTestName() { @@ -146,14 +151,14 @@ public abstract class IntegrationTest extends TestUtils { } public ClassNode getClassNodeFromFile(File file, String clsName) { - JadxDecompiler d = loadFiles(Collections.singletonList(file)); - RootNode root = JadxInternalAccess.getRoot(d); + jadxDecompiler = loadFiles(Collections.singletonList(file)); + RootNode root = JadxInternalAccess.getRoot(jadxDecompiler); - ClassNode cls = root.searchClassByName(clsName); + ClassNode cls = root.resolveClass(clsName); assertThat("Class not found: " + clsName, cls, notNullValue()); assertThat(clsName, is(cls.getClassInfo().getFullName())); - decompileAndCheck(d, Collections.singletonList(cls)); + decompileAndCheck(jadxDecompiler, Collections.singletonList(cls)); return cls; } @@ -169,12 +174,13 @@ public abstract class IntegrationTest extends TestUtils { protected JadxDecompiler loadFiles(List inputFiles) { JadxDecompiler d; + args.setInputFiles(inputFiles); + d = new JadxDecompiler(args); try { - args.setInputFiles(inputFiles); - d = new JadxDecompiler(args); d.load(); } catch (Exception e) { e.printStackTrace(); + d.close(); fail(e.getMessage()); return null; } @@ -487,6 +493,7 @@ public abstract class IntegrationTest extends TestUtils { } protected void setFallback() { + disableCompilation(); this.args.setFallbackMode(true); } diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 4d7ac5bba..55ba0b7db 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -100,7 +100,7 @@ public abstract class BaseExternalTest extends IntegrationTest { throw new JadxRuntimeException("Class process failed", e); } LOG.info("----------------------------------------------------------------"); - LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex()); + LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.getInputPath()); if (mthPattern != null) { printMethods(classNode, mthPattern); } else { diff --git a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java index 8437c6e3f..310f1a975 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/JadxClasspathTest.java @@ -26,7 +26,7 @@ public class JadxClasspathTest { @BeforeEach public void initClsp() { this.root = new RootNode(new JadxArgs()); - this.root.load(Collections.emptyList()); + this.root.loadClasses(Collections.emptyList()); this.root.initClassPath(); this.clsp = root.getClsp(); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java deleted file mode 100644 index 9942167eb..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestLoopInTry2.java +++ /dev/null @@ -1,52 +0,0 @@ -package jadx.tests.integration.others; - -import java.io.EOFException; - -import org.junit.jupiter.api.Test; - -import com.android.dex.Code; -import com.android.dx.io.instructions.DecodedInstruction; -import com.android.dx.io.instructions.ShortArrayCodeInput; - -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.DecodeException; -import jadx.tests.api.IntegrationTest; - -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; - -public class TestLoopInTry2 extends IntegrationTest { - - public static class TestCls { - private MethodNode method; - public DecodedInstruction[] insnArr; - - public void test(Code mthCode) throws DecodeException { - short[] encodedInstructions = mthCode.getInstructions(); - int size = encodedInstructions.length; - DecodedInstruction[] decoded = new DecodedInstruction[size]; - ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions); - try { - while (in.hasMore()) { - decoded[in.cursor()] = DecodedInstruction.decode(in); - } - } catch (EOFException e) { - throw new DecodeException(method, "", e); - } - insnArr = decoded; - } - } - - @Test - public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("try {")); - assertThat(code, containsOne("while (in.hasMore()) {")); - assertThat(code, containsOne("decoded[in.cursor()] = DecodedInstruction.decode(")); - assertThat(code, containsOne("} catch (EOFException e) {")); - assertThat(code, containsOne("throw new DecodeException")); - } -} diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java index a4aa17c32..53b07bea6 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally.java @@ -57,6 +57,7 @@ public class TestFinally extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); + assertThat(code, containsOne("} finally {")); assertThat(code, containsOne("cursor.getString(columnIndex);")); assertThat(code, not(containsOne("String str = true;"))); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java index 09d014d39..a30f17d46 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java @@ -49,6 +49,8 @@ public class TestTryCatchFinally8 extends IntegrationTest { String code = cls.getCode().toString(); assertThat(code, containsString("try {")); + assertThat(code, containsString("} catch (IOException e) {")); + assertThat(code, containsString("} finally {")); assertThat(code, containsString("file.delete();")); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java index d604d3b6a..f1b7f441d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchLastInsn.java @@ -2,11 +2,9 @@ package jadx.tests.integration.trycatch; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -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 TestTryCatchLastInsn extends SmaliTest { @@ -16,7 +14,7 @@ public class TestTryCatchLastInsn extends SmaliTest { ? r1 = "result"; // String try { r1 = call(); // Exception - } catch(Exception e) { + } catch (Exception e) { System.out.println(r1); // String r1 = e; } @@ -27,9 +25,9 @@ public class TestTryCatchLastInsn extends SmaliTest { @Test public void test() { - ClassNode cls = getClassNodeFromSmali(); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return call();")); + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("return call();") + .containsOne("} catch (Exception e) {"); } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 86b590858..68f4766ce 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.Collectors; -import javax.swing.ProgressMonitor; +import javax.swing.*; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -33,6 +33,7 @@ public class JadxWrapper { } public void openFile(File file) { + close(); this.openFile = file; try { JadxArgs jadxArgs = settings.toJadxArgs(); @@ -42,6 +43,17 @@ public class JadxWrapper { this.decompiler.load(); } catch (Exception e) { LOG.error("Jadx init error", e); + close(); + } + } + + public void close() { + if (decompiler != null) { + try { + decompiler.close(); + } catch (Exception e) { + LOG.error("jadx decompiler close error", e); + } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 396aa408e..9690d0a55 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1098,6 +1098,7 @@ public class MainWindow extends JFrame { settings.saveWindowPos(this); settings.setMainWindowExtendedState(getExtendedState()); cancelBackgroundJobs(); + wrapper.close(); dispose(); FileUtils.deleteTempRootDir(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index 06fe2bfbf..5241181c7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -21,9 +21,7 @@ import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; import jadx.api.JavaNode; -import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.files.InputFile; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -66,12 +64,12 @@ public class RenameDialog extends JDialog { } private Path getDeobfMapPath(RootNode root) { - List dexNodes = root.getDexNodes(); - if (dexNodes.isEmpty()) { + List inputFiles = root.getArgs().getInputFiles(); + if (inputFiles.isEmpty()) { return null; } - InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile(); - Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath(); + File firstInputFile = inputFiles.get(0); + Path inputFilePath = firstInputFile.getAbsoluteFile().toPath(); String inputName = inputFilePath.getFileName().toString(); String baseName = inputName.substring(0, inputName.lastIndexOf('.')); diff --git a/jadx-plugins/jadx-dex-input/build.gradle b/jadx-plugins/jadx-dex-input/build.gradle new file mode 100644 index 000000000..f5d10c75f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) + + testImplementation('org.smali:smali:2.4.0') +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java new file mode 100644 index 000000000..936f4cfc8 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexException.java @@ -0,0 +1,13 @@ +package jadx.plugins.input.dex; + +public class DexException extends RuntimeException { + private static final long serialVersionUID = -5575702801815409269L; + + public DexException(String message, Throwable cause) { + super(message, cause); + } + + public DexException(String message) { + super(message); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java new file mode 100644 index 000000000..925d250b9 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java @@ -0,0 +1,85 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +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.plugins.input.dex.sections.DexConsts; + +public class DexFileLoader { + private static final Logger LOG = LoggerFactory.getLogger(DexFileLoader.class); + + public static List collectDexFiles(List pathsList) { + return pathsList.stream() + .map((Path path) -> loadDexFromPath(path, 0)) + .flatMap(Collection::stream) + .peek(dr -> LOG.debug("Loading dex: {}", dr)) + .collect(Collectors.toList()); + } + + private static List loadDexFromPath(Path path, int depth) { + try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { + if (isDex(fileChannel)) { + return Collections.singletonList(new DexReader(path, fileChannel)); + } + if (depth == 0 && isZip(fileChannel)) { + return collectDexFromZip(path, depth); + } + } catch (Exception e) { + LOG.error("File open error: {}", path, e); + } + return Collections.emptyList(); + } + + private static List collectDexFromZip(Path path, int depth) throws IOException { + List result = new ArrayList<>(); + FileSystem zip = FileSystems.newFileSystem(path, (ClassLoader) null); + for (Path rootDir : zip.getRootDirectories()) { + Files.walkFileTree(rootDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + // TODO: add zip security checks + result.addAll(loadDexFromPath(file, depth + 1)); + return FileVisitResult.CONTINUE; + } + }); + } + return result; + } + + private static boolean isDex(FileChannel fileChannel) { + return isStartWithBytes(fileChannel, DexConsts.DEX_FILE_MAGIC); + } + + private static boolean isZip(FileChannel fileChannel) { + return isStartWithBytes(fileChannel, DexConsts.ZIP_FILE_MAGIC); + } + + private static boolean isStartWithBytes(FileChannel fileChannel, byte[] startBytes) { + try { + fileChannel.position(0); + ByteBuffer buf = ByteBuffer.allocate(startBytes.length); + fileChannel.read(buf); + return Arrays.equals(startBytes, buf.array()); + } catch (Exception e) { + return false; + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java new file mode 100644 index 000000000..049506827 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexInputPlugin.java @@ -0,0 +1,21 @@ +package jadx.plugins.input.dex; + +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; + +public class DexInputPlugin implements JadxInputPlugin { + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo("dex-input", "DexInput", "Load .dex and .apk files"); + } + + @Override + public ILoadResult loadFiles(List input) { + return new DexLoadResult(DexFileLoader.collectDexFiles(input)); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java new file mode 100644 index 000000000..967b91737 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexLoadResult.java @@ -0,0 +1,35 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; + +public class DexLoadResult implements ILoadResult { + private final List dexReaders; + + public DexLoadResult(List dexReaders) { + this.dexReaders = dexReaders; + } + + @Override + public void visitClasses(Consumer consumer) { + for (DexReader dexReader : dexReaders) { + dexReader.visitClasses(consumer); + } + } + + @Override + public void visitResources(Consumer consumer) { + } + + @Override + public void close() throws IOException { + for (DexReader dexReader : dexReaders) { + dexReader.close(); + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java new file mode 100644 index 000000000..4ae196da0 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java @@ -0,0 +1,96 @@ +package jadx.plugins.input.dex; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.plugins.input.dex.sections.DexClassData; +import jadx.plugins.input.dex.sections.DexHeader; +import jadx.plugins.input.dex.sections.SectionReader; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexReader implements Closeable { + + private final Path path; + private final FileChannel fileChannel; + private final ByteBuffer buf; + private final DexHeader header; + + public DexReader(Path path, FileChannel fileChannel) throws IOException { + this.path = path; + this.fileChannel = fileChannel; + this.buf = loadIntoByteBuffer(fileChannel); + this.header = new DexHeader(new SectionReader(this, 0)); + } + + private static ByteBuffer loadIntoByteBuffer(FileChannel fileChannel) throws IOException { + long size = fileChannel.size(); + if (size > Integer.MAX_VALUE) { + throw new IOException("File too big"); + } + int readSize = (int) size; + ByteBuffer buf = ByteBuffer.allocate(readSize); + fileChannel.position(0); + int read = fileChannel.read(buf); + if (read != readSize) { + throw new IOException("Failed to read whole file into buffer. Read: " + read + ", expected: " + readSize); + } + return buf; + } + + public String getDexVersion() { + return this.header.getVersion(); + } + + public void visitClasses(Consumer consumer) { + int count = header.getClassDefsSize(); + if (count == 0) { + return; + } + int classDefsOff = header.getClassDefsOff(); + SectionReader in = new SectionReader(this, classDefsOff); + AnnotationsParser annotationsParser = new AnnotationsParser(in.copy(), in.copy()); + DexClassData classData = new DexClassData(in, annotationsParser); + for (int i = 0; i < count; i++) { + consumer.accept(classData); + in.shiftOffset(DexClassData.SIZE); + } + } + + public ByteBuffer getBuf() { + return buf; + } + + public DexHeader getHeader() { + return header; + } + + public Path getPath() { + return path; + } + + public String getFullPath() { + StringBuilder sb = new StringBuilder(); + FileSystem fileSystem = path.getFileSystem(); + if (fileSystem.getClass().getName().contains("Zip")) { + sb.append(fileSystem.toString()).append(':'); + } + sb.append(path.toAbsolutePath()); + return sb.toString(); + } + + @Override + public void close() throws IOException { + this.fileChannel.close(); + } + + @Override + public String toString() { + return getFullPath(); + } +} 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 new file mode 100644 index 000000000..36fcc71bf --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnData.java @@ -0,0 +1,208 @@ +package jadx.plugins.input.dex.insns; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +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.dex.sections.DexCodeReader; +import jadx.plugins.input.dex.sections.SectionReader; + +public class DexInsnData implements InsnData { + private final DexCodeReader codeData; + private final SectionReader externalReader; + + private DexInsnInfo insnInfo; + private boolean decoded; + private int opcodeUnit; + private int length; + + private int offset; + private int[] argsReg = new int[5]; + private int regsCount; + private long literal; + private int target; + private int index; + @Nullable + private ICustomPayload payload; + + public DexInsnData(DexCodeReader codeData, SectionReader externalReader) { + this.codeData = codeData; + this.externalReader = externalReader; + } + + @Override + public void decode() { + if (insnInfo != null && !decoded) { + codeData.decode(this); + } + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public Opcode getOpcode() { + DexInsnInfo info = this.insnInfo; + if (info == null) { + return Opcode.UNKNOWN; + } + return info.getApiOpcode(); + } + + @Override + public int getRawOpcodeUnit() { + return opcodeUnit; + } + + @Override + public int getRegsCount() { + return regsCount; + } + + @Override + public int getReg(int argNum) { + return argsReg[argNum]; + } + + @Override + public long getLiteral() { + return literal; + } + + @Override + public int getTarget() { + return target; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public InsnIndexType getIndexType() { + return insnInfo.getIndexType(); + } + + @Override + public String getIndexAsString() { + return externalReader.getString(index); + } + + @Override + public String getIndexAsType() { + return externalReader.getType(index); + } + + @Override + public IFieldData getIndexAsField() { + return externalReader.getFieldData(index); + } + + @Override + public IMethodData getIndexAsMethod() { + return externalReader.getMethodData(index); + } + + @Nullable + @Override + public ICustomPayload getPayload() { + return payload; + } + + public int[] getArgsReg() { + return argsReg; + } + + public void setArgsReg(int[] argsReg) { + this.argsReg = argsReg; + } + + public void setRegsCount(int regsCount) { + this.regsCount = regsCount; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + 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 boolean isDecoded() { + return decoded; + } + + public void setDecoded(boolean decoded) { + this.decoded = decoded; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public DexInsnInfo getInsnInfo() { + return insnInfo; + } + + public void setInsnInfo(DexInsnInfo insnInfo) { + this.insnInfo = insnInfo; + } + + public DexCodeReader getCodeData() { + return codeData; + } + + public int getOpcodeUnit() { + return opcodeUnit; + } + + public void setOpcodeUnit(int opcodeUnit) { + this.opcodeUnit = opcodeUnit; + } + + public void setPayload(ICustomPayload payload) { + this.payload = payload; + } + + @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-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 new file mode 100644 index 000000000..07618abf4 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java @@ -0,0 +1,451 @@ +package jadx.plugins.input.dex.insns; + +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 { + public static final DexInsnFormat FORMAT_10X = new DexInsnFormat(1, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + // no op + } + }; + + public static final DexInsnFormat FORMAT_12X = new DexInsnFormat(1, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + } + }; + + public static final DexInsnFormat FORMAT_11N = new DexInsnFormat(1, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + insn.setLiteral(signedNibble3(opcodeUnit)); + } + }; + + public static final DexInsnFormat FORMAT_11X = new DexInsnFormat(1, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + } + }; + + public static final DexInsnFormat FORMAT_10T = new DexInsnFormat(1, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + signedByte1(opcodeUnit)); + } + }; + + public static final DexInsnFormat FORMAT_20T = new DexInsnFormat(2, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_20BC = new DexInsnFormat(2, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setLiteral(byte1(opcodeUnit)); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_22X = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + regs[1] = in.readUShort(); + } + }; + + public static final DexInsnFormat FORMAT_21T = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = signedByte1(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_21S = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_21H = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + + long literal = in.readShort(); + literal <<= (byte0(opcodeUnit) == DexOpcodes.CONST_HIGH16) ? 16 : 48; + insn.setLiteral(literal); + } + }; + + public static final DexInsnFormat FORMAT_21C = new DexInsnFormat(2, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_23X = new DexInsnFormat(2, 3) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + int next = in.readUShort(); + regs[1] = byte0(next); + regs[2] = byte1(next); + } + }; + + public static final DexInsnFormat FORMAT_22B = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + int next = in.readUShort(); + regs[1] = byte0(next); + insn.setLiteral(signedByte1(next)); + } + }; + + public static final DexInsnFormat FORMAT_22T = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_22S = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setLiteral(in.readShort()); + } + }; + + public static final DexInsnFormat FORMAT_22C = new DexInsnFormat(2, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = nibble2(opcodeUnit); + regs[1] = nibble3(opcodeUnit); + insn.setIndex(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_22CS = FORMAT_22C; + + public static final DexInsnFormat FORMAT_30T = new DexInsnFormat(3, 0) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + insn.setTarget(insn.getOffset() + in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_32X = new DexInsnFormat(3, 2) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = in.readUShort(); + regs[1] = in.readUShort(); + } + }; + + public static final DexInsnFormat FORMAT_31I = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_31T = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setTarget(insn.getOffset() + in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_31C = new DexInsnFormat(3, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setIndex(in.readInt()); + } + }; + + public static final DexInsnFormat FORMAT_35C = new DexInsnFormat(3, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsList(insn, opcodeUnit, in); + } + }; + + public static final DexInsnFormat FORMAT_35MS = FORMAT_35C; + public static final DexInsnFormat FORMAT_35MI = FORMAT_35C; + + public static final DexInsnFormat FORMAT_3RC = new DexInsnFormat(3, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsRange(insn, opcodeUnit, in); + } + }; + + public static final DexInsnFormat FORMAT_3RMS = FORMAT_3RC; + public static final DexInsnFormat FORMAT_3RMI = FORMAT_3RC; + + public static final DexInsnFormat FORMAT_45CC = new DexInsnFormat(4, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsList(insn, opcodeUnit, in); + insn.setTarget(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_4RCC = new DexInsnFormat(4, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + readRegsRange(insn, opcodeUnit, in); + insn.setTarget(in.readUShort()); + } + }; + + public static final DexInsnFormat FORMAT_51I = new DexInsnFormat(5, 1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int[] regs = insn.getArgsReg(); + regs[0] = byte1(opcodeUnit); + insn.setLiteral(in.readLong()); + } + }; + + public static final DexInsnFormat FORMAT_PACKED_SWITCH_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int size = in.readUShort(); + int firstKey = in.readInt(); + int[] keys = new int[size]; + int[] targets = new int[size]; + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + keys[i] = firstKey + i; + } + insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setLength(size * 2 + 4); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int size = in.readUShort(); + in.skip(4 + size * 4); + insn.setLength(size * 2 + 4); + } + }; + + public static final DexInsnFormat FORMAT_SPARSE_SWITCH_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int size = in.readUShort(); + int[] keys = new int[size]; + for (int i = 0; i < size; i++) { + keys[i] = in.readInt(); + } + int[] targets = new int[size]; + for (int i = 0; i < size; i++) { + targets[i] = in.readInt(); + } + insn.setPayload(new DexSwitchPayload(size, keys, targets)); + insn.setLength(size * 4 + 2); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int size = in.readUShort(); + in.skip(4 + size * 4 * 2); + insn.setLength(size * 4 + 2); + } + }; + + public static final DexInsnFormat FORMAT_FILL_ARRAY_DATA_PAYLOAD = new DexInsnFormat(-1, -1) { + @Override + public void decode(DexInsnData insn, int opcodeUnit, SectionReader in) { + int elemSize = in.readUShort(); + int size = in.readInt(); + Object data; + switch (elemSize) { + case 1: { + data = in.readByteArray(size); + if (size % 2 != 0) { + in.readUByte(); + } + break; + } + case 2: { + short[] array = new short[size]; + for (int i = 0; i < size; i++) { + array[i] = (short) in.readShort(); + } + data = array; + break; + } + case 4: { + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readInt(); + } + data = array; + break; + } + case 8: { + long[] array = new long[size]; + for (int i = 0; i < size; i++) { + array[i] = in.readLong(); + } + data = array; + break; + } + default: + throw new DexException("Unexpected element size in FILL_ARRAY_DATA_PAYLOAD: " + elemSize); + } + insn.setLength((size * elemSize + 1) / 2 + 4); + insn.setPayload(new DexArrayPayload(size, elemSize, data)); + } + + @Override + public void skip(DexInsnData insn, SectionReader in) { + int elemSize = in.readUShort(); + int size = in.readInt(); + if (size == 1) { + in.skip(size + size % 2); + } else { + in.skip(size * elemSize); + } + insn.setLength((size * elemSize + 1) / 2 + 4); + } + }; + + protected void readRegsList(DexInsnData insn, int opcodeUnit, SectionReader in) { + int regsCount1 = nibble3(opcodeUnit); + int index = in.readUShort(); + int rs = in.readUShort(); + + int[] regs = insn.getArgsReg(); + regs[0] = nibble0(rs); + regs[1] = nibble1(rs); + regs[2] = nibble2(rs); + regs[3] = nibble3(rs); + regs[4] = nibble2(opcodeUnit); + + insn.setRegsCount(regsCount1); + insn.setIndex(index); + } + + protected void readRegsRange(DexInsnData insn, int opcodeUnit, SectionReader in) { + int regsCount = byte1(opcodeUnit); + int index = in.readUShort(); + int startReg = in.readUShort(); + + int[] regs = insn.getArgsReg(); + if (regs.length < regsCount) { + regs = new int[regsCount]; + insn.setArgsReg(regs); + } + int regNum = startReg; + for (int i = 0; i < regsCount; i++) { + regs[i] = regNum; + regNum++; + } + insn.setRegsCount(regsCount); + insn.setIndex(index); + } + + private final int length; + private final int regsCount; + + protected DexInsnFormat(int length, int regsCount) { + this.length = length; + this.regsCount = regsCount; + } + + public abstract void decode(DexInsnData insn, int opcodeUnit, SectionReader in); + + public void skip(DexInsnData insn, SectionReader in) { + int len = this.length; + if (len == 1) { + return; + } + in.skip((len - 1) * 2); + } + + public int getLength() { + return length; + } + + public int getRegsCount() { + return regsCount; + } + + private static int byte0(int value) { + return value & 0xFF; + } + + private static int byte1(int value) { + return (value >> 8) & 0xFF; + } + + private static int signedByte1(int value) { + return (byte) (value >> 8); + } + + private static int nibble0(int value) { + return value & 0xF; + } + + private static int nibble1(int value) { + return (value >> 4) & 0xF; + } + + private static int nibble2(int value) { + return (value >> 8) & 0xF; + } + + private static int nibble3(int value) { + return (value >> 12) & 0xF; + } + + private static int signedNibble3(int value) { + return (((value >> 12) & 0xF) << 28) >> 28; + } +} 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 new file mode 100644 index 000000000..75dbee4ed --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexInsnInfo.java @@ -0,0 +1,350 @@ +package jadx.plugins.input.dex.insns; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.insns.InsnIndexType; +import jadx.api.plugins.input.insns.Opcode; + +public class DexInsnInfo { + + private static final DexInsnInfo[] INSN_INFO; + private static final Map PAYLOAD_INFO; + + static { + DexInsnInfo[] arr = new DexInsnInfo[0x100]; + INSN_INFO = arr; + register(arr, DexOpcodes.NOP, Opcode.NOP, DexInsnFormat.FORMAT_10X); + + register(arr, DexOpcodes.MOVE, Opcode.MOVE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_FROM16, Opcode.MOVE, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_16, Opcode.MOVE, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_WIDE, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_WIDE_FROM16, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_WIDE_16, Opcode.MOVE_WIDE, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_OBJECT, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MOVE_OBJECT_FROM16, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_22X); + register(arr, DexOpcodes.MOVE_OBJECT_16, Opcode.MOVE_OBJECT, DexInsnFormat.FORMAT_32X); + + register(arr, DexOpcodes.MOVE_RESULT, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MOVE_RESULT_WIDE, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.MOVE_EXCEPTION, Opcode.MOVE_EXCEPTION, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.RETURN_VOID, Opcode.RETURN_VOID, DexInsnFormat.FORMAT_10X); + register(arr, DexOpcodes.RETURN, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.RETURN_WIDE, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.RETURN_OBJECT, Opcode.RETURN, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.CONST_4, Opcode.CONST, DexInsnFormat.FORMAT_11N); + register(arr, DexOpcodes.CONST_16, Opcode.CONST, DexInsnFormat.FORMAT_21S); + register(arr, DexOpcodes.CONST, Opcode.CONST, DexInsnFormat.FORMAT_31I); + register(arr, DexOpcodes.CONST_HIGH16, Opcode.CONST, DexInsnFormat.FORMAT_21H); + + register(arr, DexOpcodes.CONST_WIDE_16, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_21S); + register(arr, DexOpcodes.CONST_WIDE_32, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_31I); + register(arr, DexOpcodes.CONST_WIDE, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_51I); + register(arr, DexOpcodes.CONST_WIDE_HIGH16, Opcode.CONST_WIDE, DexInsnFormat.FORMAT_21H); + + register(arr, DexOpcodes.CONST_STRING, Opcode.CONST_STRING, DexInsnFormat.FORMAT_21C, InsnIndexType.STRING_REF); + register(arr, DexOpcodes.CONST_STRING_JUMBO, Opcode.CONST_STRING, DexInsnFormat.FORMAT_31C, InsnIndexType.STRING_REF); + + register(arr, DexOpcodes.CONST_CLASS, Opcode.CONST_CLASS, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + + register(arr, DexOpcodes.MONITOR_ENTER, Opcode.MONITOR_ENTER, DexInsnFormat.FORMAT_11X); + register(arr, DexOpcodes.MONITOR_EXIT, Opcode.MONITOR_EXIT, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.CHECK_CAST, Opcode.CHECK_CAST, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.INSTANCE_OF, Opcode.INSTANCE_OF, DexInsnFormat.FORMAT_22C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.ARRAY_LENGTH, Opcode.ARRAY_LENGTH, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEW_INSTANCE, Opcode.NEW_INSTANCE, DexInsnFormat.FORMAT_21C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.NEW_ARRAY, Opcode.NEW_ARRAY, DexInsnFormat.FORMAT_22C, InsnIndexType.TYPE_REF); + + register(arr, DexOpcodes.FILLED_NEW_ARRAY, Opcode.FILLED_NEW_ARRAY, DexInsnFormat.FORMAT_35C, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.FILLED_NEW_ARRAY_RANGE, Opcode.FILLED_NEW_ARRAY_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.TYPE_REF); + register(arr, DexOpcodes.FILL_ARRAY_DATA, Opcode.FILL_ARRAY_DATA, DexInsnFormat.FORMAT_31T); + + register(arr, DexOpcodes.THROW, Opcode.THROW, DexInsnFormat.FORMAT_11X); + + register(arr, DexOpcodes.GOTO, Opcode.GOTO, DexInsnFormat.FORMAT_10T); + register(arr, DexOpcodes.GOTO_16, Opcode.GOTO, DexInsnFormat.FORMAT_20T); + register(arr, DexOpcodes.GOTO_32, Opcode.GOTO, DexInsnFormat.FORMAT_30T); + + register(arr, DexOpcodes.PACKED_SWITCH, Opcode.PACKED_SWITCH, DexInsnFormat.FORMAT_31T); + register(arr, DexOpcodes.SPARSE_SWITCH, Opcode.SPARSE_SWITCH, DexInsnFormat.FORMAT_31T); + + register(arr, DexOpcodes.CMPL_FLOAT, Opcode.CMPL_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPG_FLOAT, Opcode.CMPG_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPL_DOUBLE, Opcode.CMPL_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMPG_DOUBLE, Opcode.CMPG_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.CMP_LONG, Opcode.CMP_LONG, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.IF_EQ, Opcode.IF_EQ, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_NE, Opcode.IF_NE, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_LT, Opcode.IF_LT, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_GE, Opcode.IF_GE, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_GT, Opcode.IF_GT, DexInsnFormat.FORMAT_22T); + register(arr, DexOpcodes.IF_LE, Opcode.IF_LE, DexInsnFormat.FORMAT_22T); + + register(arr, DexOpcodes.IF_EQZ, Opcode.IF_EQZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_NEZ, Opcode.IF_NEZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_LTZ, Opcode.IF_LTZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_GEZ, Opcode.IF_GEZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_GTZ, Opcode.IF_GTZ, DexInsnFormat.FORMAT_21T); + register(arr, DexOpcodes.IF_LEZ, Opcode.IF_LEZ, DexInsnFormat.FORMAT_21T); + + register(arr, DexOpcodes.AGET, Opcode.AGET, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_WIDE, Opcode.AGET_WIDE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_OBJECT, Opcode.AGET_OBJECT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_BOOLEAN, Opcode.AGET_BOOLEAN, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_BYTE, Opcode.AGET_BYTE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_CHAR, Opcode.AGET_CHAR, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AGET_SHORT, Opcode.AGET_SHORT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.APUT, Opcode.APUT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_WIDE, Opcode.APUT_WIDE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_OBJECT, Opcode.APUT_OBJECT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_BOOLEAN, Opcode.APUT_BOOLEAN, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_BYTE, Opcode.APUT_BYTE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_CHAR, Opcode.APUT_CHAR, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.APUT_SHORT, Opcode.APUT_SHORT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.IGET, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_WIDE, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_OBJECT, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_BOOLEAN, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_BYTE, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_CHAR, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IGET_SHORT, Opcode.IGET, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.IPUT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_WIDE, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_OBJECT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_BOOLEAN, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_BYTE, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_CHAR, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.IPUT_SHORT, Opcode.IPUT, DexInsnFormat.FORMAT_22C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.SGET, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_WIDE, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_OBJECT, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_BOOLEAN, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_BYTE, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_CHAR, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SGET_SHORT, Opcode.SGET, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.SPUT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_WIDE, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_OBJECT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_BOOLEAN, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_BYTE, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_CHAR, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + register(arr, DexOpcodes.SPUT_SHORT, Opcode.SPUT, DexInsnFormat.FORMAT_21C, InsnIndexType.FIELD_REF); + + register(arr, DexOpcodes.INVOKE_VIRTUAL, Opcode.INVOKE_VIRTUAL, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_SUPER, Opcode.INVOKE_SUPER, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_DIRECT, Opcode.INVOKE_DIRECT, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_STATIC, Opcode.INVOKE_STATIC, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE, DexInsnFormat.FORMAT_35C, InsnIndexType.METHOD_REF); + + register(arr, DexOpcodes.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_VIRTUAL_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_SUPER_RANGE, Opcode.INVOKE_SUPER_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_DIRECT_RANGE, Opcode.INVOKE_DIRECT_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_STATIC_RANGE, Opcode.INVOKE_STATIC_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + register(arr, DexOpcodes.INVOKE_INTERFACE_RANGE, Opcode.INVOKE_INTERFACE_RANGE, DexInsnFormat.FORMAT_3RC, InsnIndexType.METHOD_REF); + + register(arr, DexOpcodes.NEG_INT, Opcode.NEG_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NOT_INT, Opcode.NOT_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEG_LONG, Opcode.NEG_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NOT_LONG, Opcode.NOT_LONG, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.NEG_FLOAT, Opcode.NEG_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.NEG_DOUBLE, Opcode.NEG_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.INT_TO_LONG, Opcode.INT_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_FLOAT, Opcode.INT_TO_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_DOUBLE, Opcode.INT_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.LONG_TO_INT, Opcode.LONG_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.LONG_TO_FLOAT, Opcode.LONG_TO_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.LONG_TO_DOUBLE, Opcode.LONG_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.FLOAT_TO_INT, Opcode.FLOAT_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.FLOAT_TO_LONG, Opcode.FLOAT_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.FLOAT_TO_DOUBLE, Opcode.FLOAT_TO_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.DOUBLE_TO_INT, Opcode.DOUBLE_TO_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DOUBLE_TO_LONG, Opcode.DOUBLE_TO_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DOUBLE_TO_FLOAT, Opcode.DOUBLE_TO_FLOAT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.INT_TO_BYTE, Opcode.INT_TO_BYTE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_CHAR, Opcode.INT_TO_CHAR, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.INT_TO_SHORT, Opcode.INT_TO_SHORT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_INT, Opcode.ADD_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_INT, Opcode.SUB_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_INT, Opcode.MUL_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_INT, Opcode.DIV_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_INT, Opcode.REM_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AND_INT, Opcode.AND_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.OR_INT, Opcode.OR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.XOR_INT, Opcode.XOR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHL_INT, Opcode.SHL_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHR_INT, Opcode.SHR_INT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.USHR_INT, Opcode.USHR_INT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_LONG, Opcode.ADD_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_LONG, Opcode.SUB_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_LONG, Opcode.MUL_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_LONG, Opcode.DIV_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_LONG, Opcode.REM_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.AND_LONG, Opcode.AND_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.OR_LONG, Opcode.OR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.XOR_LONG, Opcode.XOR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHL_LONG, Opcode.SHL_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SHR_LONG, Opcode.SHR_LONG, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.USHR_LONG, Opcode.USHR_LONG, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_FLOAT, Opcode.ADD_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_FLOAT, Opcode.SUB_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_FLOAT, Opcode.MUL_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_FLOAT, Opcode.DIV_FLOAT, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_FLOAT, Opcode.REM_FLOAT, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_DOUBLE, Opcode.ADD_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.SUB_DOUBLE, Opcode.SUB_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.MUL_DOUBLE, Opcode.MUL_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.DIV_DOUBLE, Opcode.DIV_DOUBLE, DexInsnFormat.FORMAT_23X); + register(arr, DexOpcodes.REM_DOUBLE, Opcode.REM_DOUBLE, DexInsnFormat.FORMAT_23X); + + register(arr, DexOpcodes.ADD_INT_2ADDR, Opcode.ADD_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_INT_2ADDR, Opcode.SUB_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_INT_2ADDR, Opcode.MUL_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_INT_2ADDR, Opcode.DIV_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_INT_2ADDR, Opcode.REM_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.AND_INT_2ADDR, Opcode.AND_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.OR_INT_2ADDR, Opcode.OR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.XOR_INT_2ADDR, Opcode.XOR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHL_INT_2ADDR, Opcode.SHL_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHR_INT_2ADDR, Opcode.SHR_INT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.USHR_INT_2ADDR, Opcode.USHR_INT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_LONG_2ADDR, Opcode.ADD_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_LONG_2ADDR, Opcode.SUB_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_LONG_2ADDR, Opcode.MUL_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_LONG_2ADDR, Opcode.DIV_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_LONG_2ADDR, Opcode.REM_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.AND_LONG_2ADDR, Opcode.AND_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.OR_LONG_2ADDR, Opcode.OR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.XOR_LONG_2ADDR, Opcode.XOR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHL_LONG_2ADDR, Opcode.SHL_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SHR_LONG_2ADDR, Opcode.SHR_LONG, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.USHR_LONG_2ADDR, Opcode.USHR_LONG, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_FLOAT_2ADDR, Opcode.ADD_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_FLOAT_2ADDR, Opcode.SUB_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_FLOAT_2ADDR, Opcode.MUL_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_FLOAT_2ADDR, Opcode.DIV_FLOAT, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_FLOAT_2ADDR, Opcode.REM_FLOAT, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_DOUBLE_2ADDR, Opcode.ADD_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.SUB_DOUBLE_2ADDR, Opcode.SUB_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.MUL_DOUBLE_2ADDR, Opcode.MUL_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.DIV_DOUBLE_2ADDR, Opcode.DIV_DOUBLE, DexInsnFormat.FORMAT_12X); + register(arr, DexOpcodes.REM_DOUBLE_2ADDR, Opcode.REM_DOUBLE, DexInsnFormat.FORMAT_12X); + + register(arr, DexOpcodes.ADD_INT_LIT16, Opcode.ADD_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.RSUB_INT, Opcode.RSUB_INT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.MUL_INT_LIT16, Opcode.MUL_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.DIV_INT_LIT16, Opcode.DIV_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.REM_INT_LIT16, Opcode.REM_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.AND_INT_LIT16, Opcode.AND_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.OR_INT_LIT16, Opcode.OR_INT_LIT, DexInsnFormat.FORMAT_22S); + register(arr, DexOpcodes.XOR_INT_LIT16, Opcode.XOR_INT_LIT, DexInsnFormat.FORMAT_22S); + + register(arr, DexOpcodes.ADD_INT_LIT8, Opcode.ADD_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.RSUB_INT_LIT8, Opcode.RSUB_INT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.MUL_INT_LIT8, Opcode.MUL_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.DIV_INT_LIT8, Opcode.DIV_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.REM_INT_LIT8, Opcode.REM_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.AND_INT_LIT8, Opcode.AND_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.OR_INT_LIT8, Opcode.OR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.XOR_INT_LIT8, Opcode.XOR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.SHL_INT_LIT8, Opcode.SHL_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.SHR_INT_LIT8, Opcode.SHR_INT_LIT, DexInsnFormat.FORMAT_22B); + register(arr, DexOpcodes.USHR_INT_LIT8, Opcode.USHR_INT_LIT, DexInsnFormat.FORMAT_22B); + + 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.CONST_METHOD_HANDLE, Opcode.CONST_METHOD_HANDLE, DexInsnFormat.FORMAT_21C); + register(arr, DexOpcodes.CONST_METHOD_TYPE, Opcode.CONST_METHOD_TYPE, DexInsnFormat.FORMAT_21C); + + PAYLOAD_INFO = new ConcurrentHashMap<>(3); + registerPayload(DexOpcodes.PACKED_SWITCH_PAYLOAD, Opcode.PACKED_SWITCH_PAYLOAD, DexInsnFormat.FORMAT_PACKED_SWITCH_PAYLOAD); + registerPayload(DexOpcodes.SPARSE_SWITCH_PAYLOAD, Opcode.SPARSE_SWITCH_PAYLOAD, DexInsnFormat.FORMAT_SPARSE_SWITCH_PAYLOAD); + registerPayload(DexOpcodes.FILL_ARRAY_DATA_PAYLOAD, Opcode.FILL_ARRAY_DATA_PAYLOAD, DexInsnFormat.FORMAT_FILL_ARRAY_DATA_PAYLOAD); + } + + private static void register(DexInsnInfo[] arr, int opcode, Opcode apiOpcode, DexInsnFormat format) { + arr[opcode] = new DexInsnInfo(opcode, apiOpcode, format, InsnIndexType.NONE); + } + + private static void register(DexInsnInfo[] arr, int opcode, Opcode apiOpcode, DexInsnFormat format, InsnIndexType indexType) { + arr[opcode] = new DexInsnInfo(opcode, apiOpcode, format, indexType); + } + + private static void registerPayload(int opcode, Opcode apiOpcode, DexInsnFormat format) { + PAYLOAD_INFO.put(opcode, new DexInsnInfo(opcode, apiOpcode, format, InsnIndexType.NONE)); + } + + @Nullable + public static DexInsnInfo get(int opcodeUnit) { + int opcode = opcodeUnit & 0xFF; + if (opcode == 0 && opcodeUnit != 0) { + return PAYLOAD_INFO.get(opcodeUnit); + } + return INSN_INFO[opcode]; + } + + private final int opcode; + private final Opcode apiOpcode; + private final DexInsnFormat format; + private final InsnIndexType indexType; + + public DexInsnInfo(int opcode, Opcode apiOpcode, DexInsnFormat format, InsnIndexType indexType) { + this.opcode = opcode; + this.apiOpcode = apiOpcode; + this.format = format; + this.indexType = indexType; + } + + public int getOpcode() { + return opcode; + } + + public Opcode getApiOpcode() { + return apiOpcode; + } + + public DexInsnFormat getFormat() { + return format; + } + + public InsnIndexType getIndexType() { + return indexType; + } + + @Override + public String toString() { + return String.format("0x%X :%d%d", opcode, format.getLength(), format.getRegsCount()); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java new file mode 100644 index 000000000..4333df9cf --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/DexOpcodes.java @@ -0,0 +1,233 @@ +package jadx.plugins.input.dex.insns; + +public class DexOpcodes { + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + public static final int INVOKE_POLYMORPHIC = 0xfa; + public static final int INVOKE_POLYMORPHIC_RANGE = 0xfb; + public static final int INVOKE_CUSTOM = 0xfc; + public static final int INVOKE_CUSTOM_RANGE = 0xfd; + public static final int CONST_METHOD_HANDLE = 0xfe; + public static final int CONST_METHOD_TYPE = 0xff; + + // payload pseudo-instructions + public static final int PACKED_SWITCH_PAYLOAD = 0x0100; + public static final int SPARSE_SWITCH_PAYLOAD = 0x0200; + public static final int FILL_ARRAY_DATA_PAYLOAD = 0x0300; +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java new file mode 100644 index 000000000..f46c72ccb --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexArrayPayload.java @@ -0,0 +1,31 @@ +package jadx.plugins.input.dex.insns.payloads; + +import jadx.api.plugins.input.insns.custom.IArrayPayload; + +public class DexArrayPayload implements IArrayPayload { + + private final int size; + private final int elemSize; + private final Object data; + + public DexArrayPayload(int size, int elemSize, Object data) { + this.size = size; + this.elemSize = elemSize; + this.data = data; + } + + @Override + public int getSize() { + return size; + } + + @Override + public int getElementSize() { + return elemSize; + } + + @Override + public Object getData() { + return data; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java new file mode 100644 index 000000000..1b0889b90 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/insns/payloads/DexSwitchPayload.java @@ -0,0 +1,31 @@ +package jadx.plugins.input.dex.insns.payloads; + +import jadx.api.plugins.input.insns.custom.ISwitchPayload; + +public class DexSwitchPayload implements ISwitchPayload { + + private final int size; + private final int[] keys; + private final int[] targets; + + public DexSwitchPayload(int size, int[] keys, int[] targets) { + this.size = size; + this.keys = keys; + this.targets = targets; + } + + @Override + public int getSize() { + return size; + } + + @Override + public int[] getKeys() { + return keys; + } + + @Override + public int[] getTargets() { + return targets; + } +} 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 new file mode 100644 index 000000000..e351e7b1b --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java @@ -0,0 +1,193 @@ +package jadx.plugins.input.dex.sections; + +import java.nio.file.Path; +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 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.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexClassData implements IClassData { + public static final int SIZE = 8 * 4; + + private final SectionReader in; + private final AnnotationsParser annotationsParser; + + public DexClassData(SectionReader sectionReader, AnnotationsParser annotationsParser) { + this.in = sectionReader; + this.annotationsParser = annotationsParser; + } + + @Override + public String getType() { + int typeIdx = in.pos(0).readInt(); + String clsType = in.getType(typeIdx); + if (clsType == null) { + throw new NullPointerException("Unknown class type"); + } + return clsType; + } + + @Override + public int getAccessFlags() { + return in.pos(4).readInt(); + } + + @Nullable + @Override + public String getSuperType() { + int typeIdx = in.pos(2 * 4).readInt(); + return in.getType(typeIdx); + } + + @Override + public List getInterfacesTypes() { + int offset = in.pos(3 * 4).readInt(); + if (offset == 0) { + return Collections.emptyList(); + } + return in.absPos(offset).readTypeList(); + } + + @Nullable + @Override + public String getSourceFile() { + int strIdx = in.pos(4 * 4).readInt(); + return in.getString(strIdx); + } + + @Override + public Path getInputPath() { + return in.getDexReader().getPath(); + } + + public int getAnnotationsOff() { + return in.pos(5 * 4).readInt(); + } + + public int getClassDataOff() { + return in.pos(6 * 4).readInt(); + } + + public int getStaticValuesOff() { + return in.pos(7 * 4).readInt(); + } + + @Override + public void visitFieldsAndMethods(Consumer fieldConsumer, Consumer mthConsumer) { + int classDataOff = getClassDataOff(); + if (classDataOff == 0) { + return; + } + SectionReader data = in.copy(classDataOff); + int staticFieldsCount = data.readUleb128(); + int instanceFieldsCount = data.readUleb128(); + int directMthCount = data.readUleb128(); + int virtualMthCount = data.readUleb128(); + + annotationsParser.setOffset(getAnnotationsOff()); + visitFields(fieldConsumer, data, staticFieldsCount, instanceFieldsCount); + visitMethods(mthConsumer, data, directMthCount, virtualMthCount); + } + + private void visitFields(Consumer fieldConsumer, SectionReader data, int staticFieldsCount, int instanceFieldsCount) { + Map annotationOffsetMap = annotationsParser.readFieldsAnnotationOffsetMap(); + DexFieldData fieldData = new DexFieldData(annotationsParser); + fieldData.setParentClassType(getType()); + readFields(fieldConsumer, data, fieldData, staticFieldsCount, annotationOffsetMap); + readFields(fieldConsumer, data, fieldData, instanceFieldsCount, annotationOffsetMap); + } + + private void readFields(Consumer fieldConsumer, SectionReader data, DexFieldData fieldData, int count, + Map annOffsetMap) { + int fieldId = 0; + for (int i = 0; i < count; i++) { + fieldId += data.readUleb128(); + int accFlags = data.readUleb128(); + in.fillFieldData(fieldData, fieldId); + fieldData.setAccessFlags(accFlags); + fieldData.setAnnotationsOffset(getOffsetFromMap(fieldId, annOffsetMap)); + fieldConsumer.accept(fieldData); + } + } + + private void visitMethods(Consumer mthConsumer, SectionReader data, int directMthCount, int virtualMthCount) { + DexMethodData methodData = new DexMethodData(annotationsParser); + Map annotationOffsetMap = annotationsParser.readMethodsAnnotationOffsetMap(); + Map paramsAnnOffsetMap = annotationsParser.readMethodParamsAnnRefOffsetMap(); + + methodData.setParentClassType(getType()); + methodData.setDirect(true); + readMethods(mthConsumer, data, methodData, directMthCount, annotationOffsetMap, paramsAnnOffsetMap); + methodData.setDirect(false); + readMethods(mthConsumer, data, methodData, virtualMthCount, annotationOffsetMap, paramsAnnOffsetMap); + } + + private void readMethods(Consumer mthConsumer, SectionReader data, DexMethodData methodData, int count, + Map annotationOffsetMap, Map paramsAnnOffsetMap) { + DexCodeReader dexCodeReader = new DexCodeReader(in.copy()); + int mthIdx = 0; + for (int i = 0; i < count; i++) { + mthIdx += data.readUleb128(); + int accFlags = data.readUleb128(); + int codeOff = data.readUleb128(); + in.fillMethodData(methodData, mthIdx); + methodData.setAccessFlags(accFlags); + if (codeOff == 0) { + methodData.setCodeReader(null); + } else { + dexCodeReader.setMthId(mthIdx); + dexCodeReader.setOffset(codeOff); + methodData.setCodeReader(dexCodeReader); + } + methodData.setAnnotationsOffset(getOffsetFromMap(mthIdx, annotationOffsetMap)); + methodData.setParamAnnotationsOffset(getOffsetFromMap(mthIdx, paramsAnnOffsetMap)); + mthConsumer.accept(methodData); + } + } + + private static int getOffsetFromMap(int idx, Map annOffsetMap) { + Integer offset = annOffsetMap.get(idx); + return offset != null ? offset : 0; + } + + @Override + public List getStaticFieldInitValues() { + int staticValuesOff = getStaticValuesOff(); + if (staticValuesOff == 0) { + return Collections.emptyList(); + } + in.absPos(staticValuesOff); + int count = in.readUleb128(); + List list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + list.add(annotationsParser.parseEncodedValue(in)); + } + return list; + } + + @Override + public List getAnnotations() { + annotationsParser.setOffset(getAnnotationsOff()); + return annotationsParser.readClassAnnotations(); + } + + @Override + public int getClassDefOffset() { + return in.pos(0).getAbsPos(); + } + + @Override + public String toString() { + return getType(); + } +} 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 new file mode 100644 index 000000000..b6bcda91e --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexCodeReader.java @@ -0,0 +1,189 @@ +package jadx.plugins.input.dex.sections; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +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.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 { + + private final SectionReader in; + private int mthId; + + public DexCodeReader(SectionReader in) { + this.in = in; + } + + @Override + public DexCodeReader copy() { + DexCodeReader copy = new DexCodeReader(in.copy()); + copy.setMthId(this.getMthId()); + return copy; + } + + public void setOffset(int offset) { + this.in.setOffset(offset); + } + + @Override + public int getRegistersCount() { + return in.pos(0).readUShort(); + } + + @Override + public int getInsnsCount() { + return in.pos(12).readInt(); + } + + @Override + public void visitInstructions(Consumer insnConsumer) { + DexInsnData insnData = new DexInsnData(this, in.copy()); + in.pos(12); + int size = in.readInt(); + int offset = 0; // in code units (2 byte) + while (offset < size) { + int opcodeUnit = in.readUShort(); + DexInsnInfo insnInfo = DexInsnInfo.get(opcodeUnit); + insnData.setOffset(offset); + insnData.setInsnInfo(insnInfo); + insnData.setOpcodeUnit(opcodeUnit); + insnData.setPayload(null); + insnData.setDecoded(false); + if (insnInfo != null) { + DexInsnFormat format = insnInfo.getFormat(); + insnData.setRegsCount(format.getRegsCount()); + insnData.setLength(format.getLength()); + } else { + insnData.setRegsCount(0); + insnData.setLength(1); + } + + insnConsumer.accept(insnData); + + if (!insnData.isDecoded()) { + skip(insnData); + } + offset += insnData.getLength(); + } + } + + public void decode(DexInsnData insn) { + DexInsnFormat format = insn.getInsnInfo().getFormat(); + format.decode(insn, insn.getOpcodeUnit(), insn.getCodeData().in); + insn.setDecoded(true); + } + + public void skip(DexInsnData insn) { + DexInsnInfo insnInfo = insn.getInsnInfo(); + if (insnInfo != null) { + DexCodeReader codeReader = insn.getCodeData(); + insnInfo.getFormat().skip(insn, codeReader.in); + } + } + + @Nullable + @Override + public IDebugInfo getDebugInfo() { + int debugOff = in.pos(8).readInt(); + if (debugOff == 0) { + return null; + } + int regsCount = getRegistersCount(); + DebugInfoParser debugInfoParser = new DebugInfoParser(in, regsCount, getInsnsCount()); + debugInfoParser.initMthArgs(regsCount, in.getMethodParamTypes(mthId)); + return debugInfoParser.process(debugOff); + } + + private int getTriesCount() { + return in.pos(6).readUShort(); + } + + private int getTriesOffset() { + int triesCount = getTriesCount(); + if (triesCount == 0) { + return -1; + } + int insnsCount = getInsnsCount(); + int padding = insnsCount % 2 == 1 ? 2 : 0; + return 4 * 4 + insnsCount * 2 + padding; + } + + @Override + public List getTries() { + int triesOffset = getTriesOffset(); + if (triesOffset == -1) { + return Collections.emptyList(); + } + int triesCount = getTriesCount(); + Map catchHandlers = getCatchHandlers(triesOffset + 8 * triesCount, in.copy()); + in.pos(triesOffset); + List triesList = new ArrayList<>(triesCount); + for (int i = 0; i < triesCount; i++) { + int startAddr = in.readInt(); + int insnsCount = in.readUShort(); + int handlerOff = in.readUShort(); + ICatch catchHandler = catchHandlers.get(handlerOff); + if (catchHandler == null) { + throw new DexException("Catch handler not found by byte offset: " + handlerOff); + } + triesList.add(new DexTryData(catchHandler, startAddr, insnsCount)); + } + return triesList; + } + + private Map getCatchHandlers(int offset, SectionReader ext) { + in.pos(offset); + int byteOffsetStart = in.getAbsPos(); + int size = in.readUleb128(); + Map map = new HashMap<>(size); + for (int i = 0; i < size; i++) { + int byteIndex = in.getAbsPos() - byteOffsetStart; + int sizeAndType = in.readSleb128(); + int handlersLen = Math.abs(sizeAndType); + int[] addr = new int[handlersLen]; + String[] types = new String[handlersLen]; + for (int h = 0; h < handlersLen; h++) { + types[h] = ext.getType(in.readUleb128()); + addr[h] = in.readUleb128(); + } + int catchAllAddr; + if (sizeAndType <= 0) { + catchAllAddr = in.readUleb128(); + } else { + catchAllAddr = -1; + } + map.put(byteIndex, new DexCatch(addr, types, catchAllAddr)); + } + return map; + } + + @Override + public int getCodeOffset() { + return in.getOffset(); + } + + public void setMthId(int mthId) { + this.mthId = mthId; + } + + public int getMthId() { + return mthId; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java new file mode 100644 index 000000000..7323574fb --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java @@ -0,0 +1,12 @@ +package jadx.plugins.input.dex.sections; + +public class DexConsts { + + public static final byte[] DEX_FILE_MAGIC = { 0x64, 0x65, 0x78, 0x0a }; // 'dex\n' + + public static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 }; + + public static final int ENDIAN_CONSTANT = 0x12345678; + + public static final int NO_INDEX = -1; +} 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 new file mode 100644 index 000000000..ce482d468 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexFieldData.java @@ -0,0 +1,77 @@ +package jadx.plugins.input.dex.sections; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.annotations.IAnnotation; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; + +public class DexFieldData implements IFieldData { + @Nullable + private final AnnotationsParser annotationsParser; + + private String parentClassType; + private String type; + private String name; + private int accessFlags; + private int annotationsOffset; + + public DexFieldData(@Nullable AnnotationsParser parser) { + this.annotationsParser = parser; + } + + @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 setAnnotationsOffset(int annotationsOffset) { + this.annotationsOffset = annotationsOffset; + } + + @Override + public List getAnnotations() { + if (annotationsParser == null) { + throw new NullPointerException("Annotation parser not initialized"); + } + return annotationsParser.readAnnotationList(annotationsOffset); + } + + @Override + public String toString() { + return getParentClassType() + "->" + getName() + ":" + getType(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java new file mode 100644 index 000000000..3dc24259d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexHeader.java @@ -0,0 +1,97 @@ +package jadx.plugins.input.dex.sections; + +import jadx.plugins.input.dex.DexException; + +public class DexHeader { + private final String version; + private final int classDefsSize; + private final int classDefsOff; + private final int stringIdsOff; + private final int typeIdsOff; + private final int typeIdsSize; + private final int fieldIdsSize; + private final int fieldIdsOff; + private final int protoIdsSize; + private final int protoIdsOff; + private final int methodIdsOff; + private final int methodIdsSize; + + public DexHeader(SectionReader buf) { + byte[] magic = buf.readByteArray(4); + version = buf.readString(3); + buf.skip(1); + int checksum = buf.readInt(); + byte[] signature = buf.readByteArray(20); + int fileSize = buf.readInt(); + int headerSize = buf.readInt(); + int endianTag = buf.readInt(); + if (endianTag != DexConsts.ENDIAN_CONSTANT) { + throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag)); + } + int linkSize = buf.readInt(); + int linkOff = buf.readInt(); + int mapListOff = buf.readInt(); + int stringIdsSize = buf.readInt(); + stringIdsOff = buf.readInt(); + typeIdsSize = buf.readInt(); + typeIdsOff = buf.readInt(); + protoIdsSize = buf.readInt(); + protoIdsOff = buf.readInt(); + fieldIdsSize = buf.readInt(); + fieldIdsOff = buf.readInt(); + methodIdsSize = buf.readInt(); + methodIdsOff = buf.readInt(); + classDefsSize = buf.readInt(); + classDefsOff = buf.readInt(); + int dataSize = buf.readInt(); + int dataOff = buf.readInt(); + } + + public String getVersion() { + return version; + } + + public int getClassDefsSize() { + return classDefsSize; + } + + public int getClassDefsOff() { + return classDefsOff; + } + + public int getStringIdsOff() { + return stringIdsOff; + } + + public int getTypeIdsOff() { + return typeIdsOff; + } + + public int getTypeIdsSize() { + return typeIdsSize; + } + + public int getFieldIdsSize() { + return fieldIdsSize; + } + + public int getFieldIdsOff() { + return fieldIdsOff; + } + + public int getProtoIdsSize() { + return protoIdsSize; + } + + public int getProtoIdsOff() { + return protoIdsOff; + } + + public int getMethodIdsOff() { + return methodIdsOff; + } + + public int getMethodIdsSize() { + return methodIdsSize; + } +} 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 new file mode 100644 index 000000000..cba773eda --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexMethodData.java @@ -0,0 +1,133 @@ +package jadx.plugins.input.dex.sections; + +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.annotations.IAnnotation; +import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; +import jadx.plugins.input.dex.smali.SmaliPrinter; +import jadx.plugins.input.dex.utils.Utils; + +public class DexMethodData implements IMethodData { + @Nullable + private final AnnotationsParser annotationsParser; + + private String parentClassType; + private String returnType; + private List argTypes; + private String name; + private int accessFlags; + private boolean isDirect; + private int annotationsOffset; + private int paramAnnotationsOffset; + + @Nullable + private DexCodeReader codeReader; + + public DexMethodData(@Nullable AnnotationsParser annotationsParser) { + this.annotationsParser = annotationsParser; + } + + @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 int getAccessFlags() { + return accessFlags; + } + + public void setAccessFlags(int accessFlags) { + this.accessFlags = accessFlags; + } + + @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 boolean isDirect() { + return isDirect; + } + + public void setDirect(boolean direct) { + isDirect = direct; + } + + @Nullable + @Override + public ICodeReader getCodeReader() { + return codeReader; + } + + public void setCodeReader(@Nullable DexCodeReader codeReader) { + this.codeReader = codeReader; + } + + @Override + public String disassembleMethod() { + return SmaliPrinter.printMethod(this); + } + + public void setAnnotationsOffset(int annotationsOffset) { + this.annotationsOffset = annotationsOffset; + } + + public void setParamAnnotationsOffset(int paramAnnotationsOffset) { + this.paramAnnotationsOffset = paramAnnotationsOffset; + } + + @Override + public List getAnnotations() { + return getAnnotationsParser().readAnnotationList(annotationsOffset); + } + + @Override + public List> getParamsAnnotations() { + return getAnnotationsParser().readAnnotationRefList(paramAnnotationsOffset); + } + + private AnnotationsParser getAnnotationsParser() { + if (annotationsParser == null) { + throw new NullPointerException("Annotation parser not initialized"); + } + return annotationsParser; + } + + @Override + public String toString() { + return getParentClassType() + "->" + getName() + + '(' + Utils.listToStr(getArgTypes()) + ")" + getReturnType(); + } +} 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 new file mode 100644 index 000000000..ebd9b4ab7 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/SectionReader.java @@ -0,0 +1,245 @@ +package jadx.plugins.input.dex.sections; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.plugins.input.dex.DexReader; +import jadx.plugins.input.dex.utils.Leb128; +import jadx.plugins.input.dex.utils.MUtf8; + +import static jadx.plugins.input.dex.sections.DexConsts.NO_INDEX; + +public class SectionReader { + private final ByteBuffer buf; + private final DexReader dexReader; + private int offset; + + public SectionReader(DexReader dexReader, int off) { + this.dexReader = dexReader; + this.offset = off; + this.buf = duplicate(dexReader.getBuf(), off); + } + + private SectionReader(SectionReader sectionReader, int off) { + this(sectionReader.dexReader, off); + } + + public SectionReader copy() { + return new SectionReader(this, offset); + } + + public SectionReader copy(int off) { + return new SectionReader(this, off); + } + + private static ByteBuffer duplicate(ByteBuffer baseBuffer, int off) { + ByteBuffer dupBuf = baseBuffer.duplicate(); + dupBuf.order(ByteOrder.LITTLE_ENDIAN); + dupBuf.position(off); + return dupBuf; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public int getOffset() { + return offset; + } + + public void shiftOffset(int shift) { + this.offset += shift; + } + + public SectionReader pos(int pos) { + buf.position(offset + pos); + return this; + } + + public SectionReader absPos(int pos) { + buf.position(pos); + return this; + } + + public int getAbsPos() { + return buf.position(); + } + + public void skip(int skip) { + int pos = buf.position(); + buf.position(pos + skip); + } + + public int readInt() { + return buf.getInt(); + } + + public long readLong() { + return buf.getLong(); + } + + public byte readByte() { + return buf.get(); + } + + public int readUByte() { + return buf.get() & 0xFF; + } + + public int readUShort() { + return buf.getShort() & 0xFFFF; + } + + public int readShort() { + return buf.getShort(); + } + + public byte[] readByteArray(int len) { + byte[] arr = new byte[len]; + buf.get(arr); + return arr; + } + + public int[] readUShortArray(int size) { + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = readUShort(); + } + return arr; + } + + public String readString(int len) { + return new String(readByteArray(len), StandardCharsets.US_ASCII); + } + + public List readTypeList() { + int size = readInt(); + if (size == 0) { + return Collections.emptyList(); + } + int[] typeIds = readUShortArray(size); + List types = new ArrayList<>(size); + for (int typeId : typeIds) { + types.add(getType(typeId)); + } + return types; + } + + @Nullable + public String getType(int idx) { + if (idx == NO_INDEX) { + return null; + } + int typeIdsOff = dexReader.getHeader().getTypeIdsOff(); + absPos(typeIdsOff + idx * 4); + int strIdx = readInt(); + return getString(strIdx); + } + + @Nullable + public String getString(int idx) { + if (idx == NO_INDEX) { + return null; + } + // TODO: make string pool cache? + int stringIdsOff = dexReader.getHeader().getStringIdsOff(); + absPos(stringIdsOff + idx * 4); + int strOff = readInt(); + absPos(strOff); + return MUtf8.decode(this); + } + + public IFieldData getFieldData(int idx) { + DexFieldData fieldData = new DexFieldData(null); + int clsTypeIdx = fillFieldData(fieldData, idx); + fieldData.setParentClassType(getType(clsTypeIdx)); + return fieldData; + } + + public int fillFieldData(DexFieldData fieldData, int idx) { + int fieldIdsOff = dexReader.getHeader().getFieldIdsOff(); + absPos(fieldIdsOff + idx * 8); + int classTypeIdx = readUShort(); + int typeIdx = readUShort(); + int nameIdx = readInt(); + fieldData.setType(getType(typeIdx)); + fieldData.setName(getString(nameIdx)); + return classTypeIdx; + } + + public IMethodData getMethodData(int idx) { + DexMethodData methodData = new DexMethodData(null); + int clsTypeIdx = fillMethodData(methodData, idx); + methodData.setParentClassType(getType(clsTypeIdx)); + return methodData; + } + + public int fillMethodData(DexMethodData methodData, int idx) { + int methodIdsOff = dexReader.getHeader().getMethodIdsOff(); + absPos(methodIdsOff + idx * 8); + int classTypeIdx = readUShort(); + int protoIdx = readUShort(); + int nameIdx = readInt(); + + int protoIdsOff = dexReader.getHeader().getProtoIdsOff(); + absPos(protoIdsOff + protoIdx * 12); + int shortyIdx = readInt(); + int returnTypeIdx = readInt(); + int paramsOff = readInt(); + + List argTypes; + if (paramsOff == 0) { + argTypes = Collections.emptyList(); + } else { + argTypes = absPos(paramsOff).readTypeList(); + } + methodData.setName(getString(nameIdx)); + methodData.setReturnType(getType(returnTypeIdx)); + methodData.setArgTypes(argTypes); + return classTypeIdx; + } + + public List getMethodParamTypes(int idx) { + int methodIdsOff = dexReader.getHeader().getMethodIdsOff(); + absPos(methodIdsOff + idx * 8 + 2); + int protoIdx = readUShort(); + + int protoIdsOff = dexReader.getHeader().getProtoIdsOff(); + absPos(protoIdsOff + protoIdx * 12 + 8); + int paramsOff = readInt(); + + if (paramsOff == 0) { + return Collections.emptyList(); + } + return absPos(paramsOff).readTypeList(); + } + + public DexReader getDexReader() { + return dexReader; + } + + public int readUleb128() { + return Leb128.readUnsignedLeb128(this); + } + + public int readUleb128p1() { + return Leb128.readUnsignedLeb128(this) - 1; + } + + public int readSleb128() { + return Leb128.readSignedLeb128(this); + } + + @Override + public String toString() { + return "SectionReader{buf=" + buf + ", offset=" + offset + '}'; + } +} 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 new file mode 100644 index 000000000..cf8a8ba6f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java @@ -0,0 +1,167 @@ +package jadx.plugins.input.dex.sections.annotations; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +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.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class AnnotationsParser { + private final SectionReader in; + private final SectionReader ext; + + private int offset; + private int fieldsCount; + private int methodsCount; + private int paramsRefCount; + + public AnnotationsParser(SectionReader in, SectionReader ext) { + this.in = in; + this.ext = ext; + } + + public void setOffset(int offset) { + this.offset = offset; + if (offset == 0) { + this.fieldsCount = 0; + this.methodsCount = 0; + this.paramsRefCount = 0; + } else { + in.setOffset(offset); + in.pos(4); + this.fieldsCount = in.readInt(); + this.methodsCount = in.readInt(); + this.paramsRefCount = in.readInt(); + } + } + + public List readClassAnnotations() { + if (offset == 0) { + return Collections.emptyList(); + } + int classAnnotationsOffset = in.absPos(offset).readInt(); + return readAnnotationList(classAnnotationsOffset); + } + + public Map readFieldsAnnotationOffsetMap() { + if (fieldsCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4); + Map map = new HashMap<>(fieldsCount); + for (int i = 0; i < fieldsCount; i++) { + int fieldIdx = in.readInt(); + int fieldAnnOffset = in.readInt(); + map.put(fieldIdx, fieldAnnOffset); + } + return map; + } + + public Map readMethodsAnnotationOffsetMap() { + if (methodsCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4 + fieldsCount * 2 * 4); + Map map = new HashMap<>(methodsCount); + for (int i = 0; i < methodsCount; i++) { + int methodIdx = in.readInt(); + int methodAnnOffset = in.readInt(); + map.put(methodIdx, methodAnnOffset); + } + return map; + } + + public Map readMethodParamsAnnRefOffsetMap() { + if (paramsRefCount == 0) { + return Collections.emptyMap(); + } + in.pos(4 * 4 + fieldsCount * 2 * 4 + methodsCount * 2 * 4); + Map map = new HashMap<>(paramsRefCount); + for (int i = 0; i < paramsRefCount; i++) { + int methodIdx = in.readInt(); + int methodAnnRefOffset = in.readInt(); + map.put(methodIdx, methodAnnRefOffset); + } + return map; + } + + public List readAnnotationList(int offset) { + if (offset == 0) { + return Collections.emptyList(); + } + in.absPos(offset); + int size = in.readInt(); + if (size == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(size); + int pos = in.getAbsPos(); + for (int i = 0; i < size; i++) { + in.absPos(pos + i * 4); + int annOffset = in.readInt(); + in.absPos(annOffset); + list.add(readAnnotation(in, ext, true)); + } + return list; + } + + public List> readAnnotationRefList(int offset) { + if (offset == 0) { + return Collections.emptyList(); + } + in.absPos(offset); + int size = in.readInt(); + if (size == 0) { + return Collections.emptyList(); + } + List> list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + int refOff = in.readInt(); + int pos = in.getAbsPos(); + list.add(readAnnotationList(refOff)); + in.absPos(pos); + } + return list; + } + + public static IAnnotation readAnnotation(SectionReader in, SectionReader ext, boolean readVisibility) { + AnnotationVisibility visibility = null; + if (readVisibility) { + int v = in.readUByte(); + visibility = getVisibilityValue(v); + } + int typeIndex = in.readUleb128(); + int size = in.readUleb128(); + Map values = new LinkedHashMap<>(size); + for (int i = 0; i < size; i++) { + String name = ext.getString(in.readUleb128()); + values.put(name, EncodedValueParser.parseValue(in, ext)); + } + String type = ext.getType(typeIndex); + return new DexAnnotation(visibility, type, values); + } + + private static AnnotationVisibility getVisibilityValue(int value) { + switch (value) { + case 0: + return AnnotationVisibility.BUILD; + case 1: + return AnnotationVisibility.RUNTIME; + case 2: + return AnnotationVisibility.SYSTEM; + default: + throw new DexException("Unknown annotation visibility value: " + value); + } + } + + public EncodedValue parseEncodedValue(SectionReader in) { + return EncodedValueParser.parseValue(in, ext); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java new file mode 100644 index 000000000..d4b42b603 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/DexAnnotation.java @@ -0,0 +1,39 @@ +package jadx.plugins.input.dex.sections.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 { + private final AnnotationVisibility visibility; + private final String type; + private final Map values; + + public DexAnnotation(AnnotationVisibility visibility, String type, Map values) { + this.visibility = visibility; + this.type = type; + this.values = values; + } + + @Override + public String getAnnotationClass() { + return type; + } + + @Override + public AnnotationVisibility getVisibility() { + return visibility; + } + + @Override + public Map getValues() { + return values; + } + + @Override + public String toString() { + return "DexAnnotation{" + visibility + ", type=" + type + ", values=" + values + '}'; + } +} 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 new file mode 100644 index 000000000..adaaad25c --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/EncodedValueParser.java @@ -0,0 +1,117 @@ +package jadx.plugins.input.dex.sections.annotations; + +import java.util.ArrayList; +import java.util.List; + +import jadx.api.plugins.input.data.annotations.EncodedType; +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class EncodedValueParser { + + private static final int ENCODED_BYTE = 0x00; + private static final int ENCODED_SHORT = 0x02; + private static final int ENCODED_CHAR = 0x03; + private static final int ENCODED_INT = 0x04; + private static final int ENCODED_LONG = 0x06; + private static final int ENCODED_FLOAT = 0x10; + private static final int ENCODED_DOUBLE = 0x11; + private static final int ENCODED_STRING = 0x17; + private static final int ENCODED_TYPE = 0x18; + private static final int ENCODED_FIELD = 0x19; + private static final int ENCODED_ENUM = 0x1b; + private static final int ENCODED_METHOD = 0x1a; + private static final int ENCODED_ARRAY = 0x1c; + private static final int ENCODED_ANNOTATION = 0x1d; + private static final int ENCODED_NULL = 0x1e; + private static final int ENCODED_BOOLEAN = 0x1f; + + static EncodedValue parseValue(SectionReader in, SectionReader ext) { + int argAndType = in.readUByte(); + int type = argAndType & 0x1F; + int arg = (argAndType & 0xE0) >> 5; + int size = arg + 1; + + switch (type) { + case ENCODED_NULL: + return EncodedValue.NULL; + + case ENCODED_BOOLEAN: + return new EncodedValue(EncodedType.ENCODED_BOOLEAN, arg == 1); + case ENCODED_BYTE: + return new EncodedValue(EncodedType.ENCODED_BYTE, in.readByte()); + + case ENCODED_SHORT: + return new EncodedValue(EncodedType.ENCODED_SHORT, (short) parseNumber(in, size, true)); + case ENCODED_CHAR: + return new EncodedValue(EncodedType.ENCODED_CHAR, (char) parseUnsignedInt(in, size)); + case ENCODED_INT: + return new EncodedValue(EncodedType.ENCODED_INT, (int) parseNumber(in, size, true)); + case ENCODED_LONG: + return new EncodedValue(EncodedType.ENCODED_LONG, parseNumber(in, size, true)); + + case ENCODED_FLOAT: + return new EncodedValue(EncodedType.ENCODED_FLOAT, Float.intBitsToFloat((int) parseNumber(in, size, false, 4))); + case ENCODED_DOUBLE: + return new EncodedValue(EncodedType.ENCODED_DOUBLE, Double.longBitsToDouble(parseNumber(in, size, false, 8))); + + case ENCODED_STRING: + return new EncodedValue(EncodedType.ENCODED_STRING, ext.getString(parseUnsignedInt(in, size))); + + case ENCODED_TYPE: + return new EncodedValue(EncodedType.ENCODED_TYPE, ext.getType(parseUnsignedInt(in, size))); + + case ENCODED_METHOD: + return new EncodedValue(EncodedType.ENCODED_METHOD, ext.getMethodData(parseUnsignedInt(in, size))); + + case ENCODED_FIELD: + case ENCODED_ENUM: + return new EncodedValue(EncodedType.ENCODED_FIELD, ext.getFieldData(parseUnsignedInt(in, size))); + + case ENCODED_ARRAY: + int count = in.readUleb128(); + List values = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + values.add(parseValue(in, ext)); + } + return new EncodedValue(EncodedType.ENCODED_ARRAY, values); + + case ENCODED_ANNOTATION: + return new EncodedValue(EncodedType.ENCODED_ANNOTATION, AnnotationsParser.readAnnotation(in, ext, false)); + + default: + throw new DexException("Unknown encoded value type: 0x" + Integer.toHexString(type)); + } + } + + private static int parseUnsignedInt(SectionReader in, int byteCount) { + return (int) parseNumber(in, byteCount, false, 0); + } + + private static long parseNumber(SectionReader in, int byteCount, boolean isSignExtended) { + return parseNumber(in, byteCount, isSignExtended, 0); + } + + private static long parseNumber(SectionReader in, int byteCount, boolean isSignExtended, int fillOnRight) { + long result = 0; + long last = 0; + for (int i = 0; i < byteCount; i++) { + last = in.readUByte(); + result |= last << (i * 8); + } + if (fillOnRight != 0) { + for (int i = byteCount; i < fillOnRight; i++) { + result <<= 8; + } + } else { + if (isSignExtended && (last & 0x80) != 0) { + for (int i = byteCount; i < 8; i++) { + result |= (long) 0xFF << (i * 8); + } + } + } + return result; + } + +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java new file mode 100644 index 000000000..955dfb45f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfo.java @@ -0,0 +1,33 @@ +package jadx.plugins.input.dex.sections.debuginfo; + +import java.util.List; +import java.util.Map; + +import jadx.api.plugins.input.data.IDebugInfo; +import jadx.api.plugins.input.data.ILocalVar; + +public class DebugInfo implements IDebugInfo { + + private final Map sourceLineMap; + private final List localVars; + + public DebugInfo(Map sourceLineMap, List localVars) { + this.sourceLineMap = sourceLineMap; + this.localVars = localVars; + } + + @Override + public Map getSourceLineMapping() { + return sourceLineMap; + } + + @Override + public List getLocalVars() { + return localVars; + } + + @Override + public String toString() { + return "DebugInfo{sourceLineMap=" + sourceLineMap + ", localVars=" + localVars + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java similarity index 56% rename from jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java rename to jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java index e0f225221..5a48d54e5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/DebugInfoParser.java @@ -1,15 +1,16 @@ -package jadx.core.dex.visitors.debuginfo; +package jadx.plugins.input.dex.sections.debuginfo; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; -import com.android.dex.Dex.Section; +import org.jetbrains.annotations.Nullable; -import jadx.core.dex.attributes.nodes.SourceFileAttr; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.DexNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; +import jadx.api.plugins.input.data.ILocalVar; +import jadx.plugins.input.dex.sections.DexConsts; +import jadx.plugins.input.dex.sections.SectionReader; public class DebugInfoParser { private static final int DBG_END_SEQUENCE = 0x00; @@ -30,46 +31,73 @@ public class DebugInfoParser { // the number of line increments represented private static final int DBG_LINE_RANGE = 15; - private final MethodNode mth; - private final Section section; - private final DexNode dex; + private final SectionReader in; + private final SectionReader ext; private final LocalVar[] locals; - private final InsnNode[] insnByOffset; + private final int codeSize; - private List resultList; + private List resultList; + private Map linesMap; + @Nullable + private String sourceFile; - public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) { - this.mth = mth; - this.dex = mth.dex(); - this.section = dex.openSection(debugOffset); + private List argTypes; + private int[] argRegs; - int regsCount = mth.getRegsCount(); + public DebugInfoParser(SectionReader in, int regsCount, int codeSize) { + this.in = in; + this.ext = in.copy(); this.locals = new LocalVar[regsCount]; - this.insnByOffset = insnByOffset; + this.codeSize = codeSize; } - public List process() { + public void initMthArgs(int regsCount, List argTypes) { + if (argTypes.isEmpty()) { + this.argTypes = Collections.emptyList(); + return; + } + + int argsCount = argTypes.size(); + int[] argRegsArr = new int[argsCount]; + int regNum = regsCount; + for (int i = argsCount - 1; i >= 0; i--) { + regNum -= getTypeLen(argTypes.get(i)); + argRegsArr[i] = regNum; + } + this.argRegs = argRegsArr; + this.argTypes = argTypes; + } + + public static int getTypeLen(String type) { + switch (type.charAt(0)) { + case 'J': + case 'D': + return 2; + default: + return 1; + } + } + + public DebugInfo process(int debugOff) { + in.absPos(debugOff); + boolean varsInfoFound = false; resultList = new ArrayList<>(); + linesMap = new HashMap<>(); int addr = 0; - int line = section.readUleb128(); - - int paramsCount = section.readUleb128(); - List mthArgs = mth.getArgRegs(); + int line = in.readUleb128(); + int paramsCount = in.readUleb128(); + int argsCount = argTypes.size(); for (int i = 0; i < paramsCount; i++) { - int nameId = section.readUleb128() - 1; - if (nameId != DexNode.NO_INDEX) { - String name = dex.getString(nameId); - if (i < mthArgs.size() && name != null) { - RegisterArg arg = mthArgs.get(i); - int regNum = arg.getRegNum(); - LocalVar lVar = new LocalVar(regNum, name, arg.getInitType()); - startVar(lVar, -1); - varsInfoFound = true; - } + int nameId = in.readUleb128p1(); + String name = ext.getString(nameId); + if (name != null && i < argsCount) { + int regNum = argRegs[i]; + startVar(new LocalVar(regNum, name, argTypes.get(i)), -1); + varsInfoFound = true; } } @@ -77,47 +105,47 @@ public class DebugInfoParser { addrChange(-1, 1, line); setLine(addr, line); - int c = section.readByte() & 0xFF; + int c = in.readUByte(); while (c != DBG_END_SEQUENCE) { switch (c) { case DBG_ADVANCE_PC: { - int addrInc = section.readUleb128(); + int addrInc = in.readUleb128(); addr = addrChange(addr, addrInc, line); setLine(addr, line); break; } case DBG_ADVANCE_LINE: { - line += section.readSleb128(); + line += in.readSleb128(); break; } case DBG_START_LOCAL: { - int regNum = section.readUleb128(); - int nameId = section.readUleb128() - 1; - int type = section.readUleb128() - 1; - LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX); + 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); startVar(var, addr); varsInfoFound = true; break; } case DBG_START_LOCAL_EXTENDED: { - int regNum = section.readUleb128(); - int nameId = section.readUleb128() - 1; - int type = section.readUleb128() - 1; - int sign = section.readUleb128() - 1; - LocalVar var = new LocalVar(dex, regNum, nameId, type, sign); + int regNum = in.readUleb128(); + int nameId = in.readUleb128p1(); + int type = in.readUleb128p1(); + int sign = in.readUleb128p1(); + LocalVar var = new LocalVar(ext, regNum, nameId, type, sign); startVar(var, addr); varsInfoFound = true; break; } case DBG_RESTART_LOCAL: { - int regNum = section.readUleb128(); + int regNum = in.readUleb128(); restartVar(regNum, addr); varsInfoFound = true; break; } case DBG_END_LOCAL: { - int regNum = section.readUleb128(); + int regNum = in.readUleb128(); LocalVar var = locals[regNum]; if (var != null) { endVar(var, addr); @@ -132,11 +160,8 @@ public class DebugInfoParser { break; case DBG_SET_FILE: { - int idx = section.readUleb128() - 1; - if (idx != DexNode.NO_INDEX) { - String sourceFile = dex.getString(idx); - mth.addAttr(new SourceFileAttr(sourceFile)); - } + int idx = in.readUleb128() - 1; + this.sourceFile = ext.getString(idx); break; } @@ -149,24 +174,24 @@ public class DebugInfoParser { break; } } - c = section.readByte() & 0xFF; + c = in.readUByte(); } if (varsInfoFound) { for (LocalVar var : locals) { if (var != null && !var.isEnd()) { - endVar(var, mth.getCodeSize() - 1); + endVar(var, codeSize - 1); } } } - setSourceLines(addr, insnByOffset.length, line); + setSourceLines(addr, codeSize, line); - return resultList; + return new DebugInfo(linesMap, resultList); } private int addrChange(int addr, int addrInc, int line) { int newAddr = addr + addrInc; - int maxAddr = insnByOffset.length - 1; + int maxAddr = codeSize - 1; newAddr = Math.min(newAddr, maxAddr); setSourceLines(addr, newAddr, line); return newAddr; @@ -179,20 +204,15 @@ public class DebugInfoParser { } private void setLine(int offset, int line) { - InsnNode insn = insnByOffset[offset]; - if (insn != null) { - insn.setSourceLine(line); - } + linesMap.put(offset, line); } private void restartVar(int regNum, int addr) { LocalVar prev = locals[regNum]; if (prev != null) { endVar(prev, addr); - LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType()); + LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType(), prev.getSignature()); startVar(newVar, addr); - } else { - mth.addComment("Debug info: failed to restart local var, previous not found, register: " + regNum); } } 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/LocalVar.java new file mode 100644 index 000000000..4e8930b12 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/debuginfo/LocalVar.java @@ -0,0 +1,107 @@ +package jadx.plugins.input.dex.sections.debuginfo; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.ILocalVar; +import jadx.plugins.input.dex.sections.SectionReader; +import jadx.plugins.input.dex.utils.Utils; + +public class LocalVar implements ILocalVar { + private final int regNum; + private final String name; + private final String type; + @Nullable + private final String sign; + + private boolean isEnd; + private int startOffset; + private int endOffset; + + public LocalVar(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) { + this(regNum, name, type, null); + } + + public LocalVar(int regNum, String name, String type, @Nullable String sign) { + this.regNum = regNum; + this.name = name; + this.type = type; + this.sign = sign; + } + + public void start(int addr) { + this.isEnd = false; + this.startOffset = addr; + } + + /** + * Sets end address of local variable + * + * @param addr address + * @return true if local variable was active, else false + */ + public boolean end(int addr) { + if (isEnd) { + return false; + } + this.isEnd = true; + this.endOffset = addr; + return true; + } + + @Override + public int getRegNum() { + return regNum; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getType() { + return type; + } + + @Nullable + @Override + public String getSignature() { + return sign; + } + + @Override + public int getStartOffset() { + return startOffset; + } + + @Override + public int getEndOffset() { + return endOffset; + } + + public boolean isEnd() { + return isEnd; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return 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 new file mode 100644 index 000000000..c9ebc0eee --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexCatch.java @@ -0,0 +1,30 @@ +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/sections/trycatch/DexTryData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java new file mode 100644 index 000000000..fcb606c2a --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/trycatch/DexTryData.java @@ -0,0 +1,32 @@ +package jadx.plugins.input.dex.sections.trycatch; + +import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.input.data.ITry; + +public class DexTryData implements ITry { + + private final ICatch catchHandler; + private final int startAddr; + private final int insnsCount; + + public DexTryData(ICatch catchHandler, int startAddr, int insnsCount) { + this.catchHandler = catchHandler; + this.startAddr = startAddr; + this.insnsCount = insnsCount; + } + + @Override + public ICatch getCatch() { + return catchHandler; + } + + @Override + public int getStartAddress() { + return startAddr; + } + + @Override + public int getInstructionCount() { + return insnsCount; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java new file mode 100644 index 000000000..abf14dc5d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatter.java @@ -0,0 +1,5 @@ +package jadx.plugins.input.dex.smali; + +interface InsnFormatter { + void format(InsnFormatterInfo insnFormatInfo); +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java new file mode 100644 index 000000000..7961702b6 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/InsnFormatterInfo.java @@ -0,0 +1,49 @@ +package jadx.plugins.input.dex.smali; + +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.InsnData; + +public class InsnFormatterInfo { + private final SmaliCodeWriter codeWriter; + @Nullable + private IMethodData mth; + @Nullable + private InsnData insn; + + public InsnFormatterInfo(SmaliCodeWriter codeWriter, IMethodData mth) { + this.codeWriter = codeWriter; + this.mth = Objects.requireNonNull(mth); + } + + public InsnFormatterInfo(SmaliCodeWriter codeWriter, InsnData insn) { + this.codeWriter = codeWriter; + this.insn = Objects.requireNonNull(insn); + } + + public SmaliCodeWriter getCodeWriter() { + return codeWriter; + } + + public void setMth(IMethodData mth) { + this.mth = mth; + } + + public IMethodData getMth() { + return mth; + } + + public InsnData getInsn() { + if (insn == null) { + throw new NullPointerException("Instruction not set for formatter"); + } + return insn; + } + + public void setInsn(InsnData insn) { + this.insn = insn; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java new file mode 100644 index 000000000..dd6a56838 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliCodeWriter.java @@ -0,0 +1,77 @@ +package jadx.plugins.input.dex.smali; + +import java.util.List; + +public class SmaliCodeWriter { + public static final String NL = System.getProperty("line.separator"); + public static final String INDENT_STR = " "; + + private final StringBuilder code = new StringBuilder(); + + private int indent; + private String indentStr = ""; + + public SmaliCodeWriter startLine(String line) { + startLine(); + code.append(line); + return this; + } + + public SmaliCodeWriter startLine() { + if (code.length() != 0) { + code.append(NL); + code.append(indentStr); + } + return this; + } + + public SmaliCodeWriter add(Object obj) { + code.append(obj); + return this; + } + + public SmaliCodeWriter add(int i) { + code.append(i); + return this; + } + + public SmaliCodeWriter add(char c) { + code.append(c); + return this; + } + + public SmaliCodeWriter add(String str) { + code.append(str); + return this; + } + + public SmaliCodeWriter addArgs(List argTypes) { + for (String type : argTypes) { + code.append(type); + } + return this; + } + + public void incIndent() { + this.indent++; + buildIndent(); + } + + public void decIndent() { + this.indent--; + buildIndent(); + } + + private void buildIndent() { + StringBuilder s = new StringBuilder(indent * INDENT_STR.length()); + for (int i = 0; i < indent; i++) { + s.append(INDENT_STR); + } + this.indentStr = s.toString(); + } + + public String getCode() { + return code.toString(); + } + +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java new file mode 100644 index 000000000..11fe0c350 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliInsnFormat.java @@ -0,0 +1,155 @@ +package jadx.plugins.input.dex.smali; + +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; + +import jadx.api.plugins.input.insns.InsnData; +import jadx.plugins.input.dex.insns.DexOpcodes; + +public class SmaliInsnFormat { + + private static SmaliInsnFormat instance; + + public static synchronized SmaliInsnFormat getInstance() { + SmaliInsnFormat instance = SmaliInsnFormat.instance; + if (instance == null) { + instance = new SmaliInsnFormat(); + SmaliInsnFormat.instance = instance; + } + return instance; + } + + private final Map formatters; + + public SmaliInsnFormat() { + formatters = registerFormatters(); + } + + private Map registerFormatters() { + Map map = new HashMap<>(); + map.put(DexOpcodes.NOP, fi -> fi.getCodeWriter().add("nop")); + map.put(DexOpcodes.SGET_OBJECT, staticFieldInsn("sget-object")); + map.put(DexOpcodes.SPUT_BOOLEAN, staticFieldInsn("sput-boolean")); + map.put(DexOpcodes.CONST, constInsn("const")); + map.put(DexOpcodes.CONST_HIGH16, constInsn("const/high16")); + map.put(DexOpcodes.CONST_STRING, stringInsn("const-string")); + map.put(DexOpcodes.INVOKE_VIRTUAL, invokeInsn("invoke-virtual")); + map.put(DexOpcodes.INVOKE_DIRECT, invokeInsn("invoke-direct")); + map.put(DexOpcodes.INVOKE_SUPER, invokeInsn("invoke-super")); + map.put(DexOpcodes.INVOKE_STATIC, invokeInsn("invoke-static")); + map.put(DexOpcodes.MOVE_RESULT, oneArgsInsn("move-result")); + map.put(DexOpcodes.RETURN_VOID, noArgsInsn("return-void")); + map.put(DexOpcodes.GOTO, gotoInsn("goto")); + map.put(DexOpcodes.GOTO_16, gotoInsn("goto-16")); + map.put(DexOpcodes.MOVE, simpleInsn("move")); + // TODO: complete list + return map; + } + + private InsnFormatter simpleInsn(String name) { + return fi -> { + SmaliCodeWriter code = fi.getCodeWriter(); + code.add(name); + InsnData insn = fi.getInsn(); + int regsCount = insn.getRegsCount(); + for (int i = 0; i < regsCount; i++) { + if (i == 0) { + code.add(' '); + } else { + code.add(", "); + } + code.add(regAt(fi, i)); + } + }; + } + + private InsnFormatter gotoInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(" :goto").add(Integer.toHexString(fi.getInsn().getTarget())); + } + + @NotNull + private InsnFormatter staticFieldInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(field(fi)); + } + + @NotNull + private InsnFormatter constInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(literal(fi)); + } + + @NotNull + private InsnFormatter stringInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)).add(", ").add(str(fi)); + } + + @NotNull + private InsnFormatter invokeInsn(String name) { + return fi -> { + SmaliCodeWriter code = fi.getCodeWriter(); + code.add(name).add(' '); + regsList(code, fi.getInsn()); + code.add(", ").add(method(fi)); + }; + } + + private InsnFormatter oneArgsInsn(String name) { + return fi -> fi.getCodeWriter().add(name).add(' ').add(regAt(fi, 0)); + } + + private InsnFormatter noArgsInsn(String name) { + return (fi) -> fi.getCodeWriter().add(name); + } + + private String literal(InsnFormatterInfo fi) { + return "0x" + Long.toHexString(fi.getInsn().getLiteral()); + } + + private String str(InsnFormatterInfo fi) { + return "\"" + fi.getInsn().getIndexAsString() + "\""; + } + + private String field(InsnFormatterInfo fi) { + return fi.getInsn().getIndexAsField().toString(); + } + + private String method(InsnFormatterInfo fi) { + return fi.getInsn().getIndexAsMethod().toString(); + } + + private void regsList(SmaliCodeWriter code, InsnData insn) { + int argsCount = insn.getRegsCount(); + code.add('{'); + for (int i = 0; i < argsCount; i++) { + if (i != 0) { + code.add(", "); + } + code.add("v").add(insn.getReg(i)); + } + code.add('}'); + } + + private String regAt(InsnFormatterInfo fi, int argNum) { + return "v" + fi.getInsn().getReg(argNum); + } + + public void format(InsnFormatterInfo formatInfo) { + InsnData insn = formatInfo.getInsn(); + insn.decode(); + int rawOpcodeUnit = insn.getRawOpcodeUnit(); + int opcode = rawOpcodeUnit & 0xFF; + InsnFormatter insnFormatter = formatters.get(opcode); + if (insnFormatter != null) { + insnFormatter.format(formatInfo); + } else { + formatInfo.getCodeWriter().add("# ").add(insn.getOpcode()).add(" (?0x").add(Integer.toHexString(rawOpcodeUnit)).add(')'); + } + } + + public String format(InsnData insn) { + InsnFormatterInfo formatInfo = new InsnFormatterInfo(new SmaliCodeWriter(), insn); + format(formatInfo); + return formatInfo.getCodeWriter().getCode(); + } +} 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 new file mode 100644 index 000000000..3f10cfe35 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/smali/SmaliPrinter.java @@ -0,0 +1,36 @@ +package jadx.plugins.input.dex.smali; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.plugins.input.dex.sections.DexMethodData; + +import static jadx.api.plugins.input.data.AccessFlagsScope.METHOD; + +// TODO: not finished +public class SmaliPrinter { + + public static String printMethod(DexMethodData mth) { + SmaliCodeWriter codeWriter = new SmaliCodeWriter(); + codeWriter.startLine(".method "); + codeWriter.add(AccessFlags.format(mth.getAccessFlags(), METHOD)); + codeWriter.add(mth.getName()); + codeWriter.add('(').addArgs(mth.getArgTypes()).add(')'); + codeWriter.add(mth.getReturnType()); + codeWriter.incIndent(); + + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader != null) { + codeWriter.startLine(".registers ").add(codeReader.getRegistersCount()); + SmaliInsnFormat insnFormat = SmaliInsnFormat.getInstance(); + InsnFormatterInfo formatterInfo = new InsnFormatterInfo(codeWriter, mth); + codeReader.visitInstructions(insn -> { + codeWriter.startLine(); + formatterInfo.setInsn(insn); + insnFormat.format(formatterInfo); + }); + codeWriter.decIndent(); + } + codeWriter.startLine(".end method"); + return codeWriter.getCode(); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java new file mode 100644 index 000000000..538e0ab5f --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Leb128.java @@ -0,0 +1,45 @@ +package jadx.plugins.input.dex.utils; + +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public final class Leb128 { + + public static int readSignedLeb128(SectionReader in) { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + do { + cur = in.readUByte(); + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("Invalid LEB128 sequence"); + } + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0) { + result |= signBits; + } + return result; + } + + public static int readUnsignedLeb128(SectionReader in) { + int result = 0; + int cur; + int count = 0; + do { + cur = in.readUByte(); + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new DexException("Invalid LEB128 sequence"); + } + return result; + } +} diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java new file mode 100644 index 000000000..e83b0a322 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/MUtf8.java @@ -0,0 +1,40 @@ +package jadx.plugins.input.dex.utils; + +import jadx.plugins.input.dex.DexException; +import jadx.plugins.input.dex.sections.SectionReader; + +public class MUtf8 { + + public static String decode(SectionReader in) { + int len = in.readUleb128(); + char[] out = new char[len]; + int k = 0; + while (true) { + char a = (char) (in.readUByte() & 0xff); + if (a == 0) { + return new String(out, 0, k); + } + out[k] = a; + if (a < '\u0080') { + k++; + } else if ((a & 0xE0) == 0xC0) { + int b = in.readUByte(); + if ((b & 0xC0) != 0x80) { + throw new DexException("Bad second byte"); + } + out[k] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); + k++; + } else if ((a & 0xF0) == 0xE0) { + int b = in.readUByte(); + int c = in.readUByte(); + if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { + throw new DexException("Bad second or third byte"); + } + out[k] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); + k++; + } else { + throw new DexException("Bad byte"); + } + } + } +} 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 new file mode 100644 index 000000000..a0881a2c5 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/utils/Utils.java @@ -0,0 +1,23 @@ +package jadx.plugins.input.dex.utils; + +import java.util.Iterator; +import java.util.List; + +public class Utils { + + public static String listToStr(List collection) { + 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/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..a8b08d540 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.dex.DexInputPlugin 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 new file mode 100644 index 000000000..5896d910e --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/DexInputPluginTest.java @@ -0,0 +1,73 @@ +package jadx.plugins.input.dex; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import jadx.api.plugins.input.data.AccessFlags; +import jadx.api.plugins.input.data.AccessFlagsScope; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.plugins.input.dex.utils.SmaliUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +class DexInputPluginTest { + + @Test + public void loadSampleApk() throws Exception { + processFile(Paths.get(ClassLoader.getSystemResource("samples/app-with-fake-dex.apk").toURI())); + } + + @Test + public void loadHelloWorld() throws Exception { + processFile(Paths.get(ClassLoader.getSystemResource("samples/hello.dex").toURI())); + } + + @Test + public void loadTestSmali() throws Exception { + processFile(SmaliUtils.compileSmaliFromResource("samples/test.smali")); + } + + private static void processFile(Path sample) throws IOException { + System.out.println("Input file: " + sample.toAbsolutePath()); + long start = System.currentTimeMillis(); + List files = Collections.singletonList(sample); + try (ILoadResult result = new DexInputPlugin().loadFiles(files)) { + AtomicInteger count = new AtomicInteger(); + result.visitClasses(cls -> { + System.out.println(); + System.out.println("Class: " + cls.getType()); + 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()); + count.getAndIncrement(); + + cls.visitFieldsAndMethods( + System.out::println, + mth -> { + System.out.println("---"); + System.out.println(mth); + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader != null) { + codeReader.visitInstructions(insn -> { + insn.decode(); + System.out.println(insn); + }); + } + System.out.println("---"); + System.out.println(mth.disassembleMethod()); + System.out.println("---"); + }); + }); + assertThat(count.get()).isGreaterThan(0); + } + System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms"); + } +} diff --git a/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java new file mode 100644 index 000000000..cc784d89d --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/java/jadx/plugins/input/dex/utils/SmaliUtils.java @@ -0,0 +1,47 @@ +package jadx.plugins.input.dex.utils; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.jf.smali.Smali; +import org.jf.smali.SmaliOptions; + +public class SmaliUtils { + + public static Path compileSmaliFromResource(String res) { + try { + Path input = Paths.get(ClassLoader.getSystemResource(res).toURI()); + return compileSmali(input); + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } + + public static Path compileSmali(Path input) { + try { + Path tempFile = Files.createTempFile("jadx", "smali.dex"); + compileSmali(tempFile, Collections.singletonList(input)); + return tempFile; + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } + + private static void compileSmali(Path output, List inputFiles) { + try { + SmaliOptions options = new SmaliOptions(); + options.outputDexFile = output.toAbsolutePath().toString(); + List inputFileNames = inputFiles.stream() + .map(Path::toAbsolutePath) + .map(Path::toString) + .collect(Collectors.toList()); + Smali.assemble(options, inputFileNames); + } catch (Exception e) { + throw new AssertionError("Smali assemble error", e); + } + } +} diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk b/jadx-plugins/jadx-dex-input/src/test/resources/samples/app-with-fake-dex.apk new file mode 100644 index 0000000000000000000000000000000000000000..0a6969ad69c712a10b945a720ba7c24b6060ca50 GIT binary patch literal 10030 zcmeHNc{o*T+h1(kCS;z85OQP|B10iE9Ajqo#x~EknGBgTrVw_9Mwv1+kxHf{b47;8 zOd&+ZywB3<)bXD0{o{MjAK&#|-Pd*R`?>G6p5OiK-+I=w_qv~@qk$lzgCK|mC^nCh)M`<)+Bh94s!FXAJ#xO5zu=D1)yEa1mgK_*I+=Mk z2x?-k&y7zvREfFPLgt0cVl}o8Jg`r%T zp#YbatL1lPS)aJON5#b}S6w{*mgs#XlZ(5eO^|pdP3%^Ra|%U@!io8akF;?oQa+@F zD5-IDSv_AP+CoGSj3Zn}n5L2SXgb?E84qf6#Qi$NCn<+Zv3h8R;s~=5)F-BoCuH$q z1Fd0>DJX9=sX3x($lnzMt5ov$ggb2SnLH;m} z5wWnKTL+Y(P40bt9EoTt^r?#B)v#JLhXa8q6>78Yd|rK;dGtwH^fq;{UuVz#JdTKV zAGecV^65!VJ!4PNYG=Ds+a5>D{;7L_^SyRpOIowsx)@JJ2*pz*jtOmyBl2K3C)Q7> zHi>^VH7bz}o($3VU43^D)UO+QG9S9pJ(hemFLinRR z#9z;t-Dy_ZKeChinNQ>ZG0TXjQ3hpQUp#?0%f&qN}I#c z#XgP+rDJ`|5|YGj*Idtanr3KaR$>5ITvj`7Cfa}K;_YJZN>%)D@15Zy%uZqSwi3mr zu;D6a;Gyt49$bO5a`(*W$mHT(=^K6HtyvF}=6!KPx7gUWYw1MnOJZ zfju&&rjO2(YwS=7XGVHqx!6(ujLE~%K5r&UD~0^< zJ`X=65XK*?OySejSM!=m-_jO8*u%tMSW%*PjXy*1wKtih$x^}XYZR^Ni8;sOWONTZ z6&}48l~`&kxk95pPnZ`b-b`2V`jAwedj^9@go;TF)rY9Z#>NS;_^cBZ zwS>gf(2v$v2pxzx+uK#Ycx;-Iy;lA}^y9hdRR80jKS+Bc;}y+{(o8KKW!}9xaW-G& z)8RX1{I^_Gs28cJ81ZEt>l=7?G?FhlXun0HO8LGuP3vvdsI!$Pb_z~Bp}=>Pm2tW* z$d7DDn-|81>d^00349GO46YSu_oIq=v0S+|IVPVm91_ei=HUD8tYGVc!ejeZcg4s0 zUDzph{;C>3wkODt&W9$CTZ!0SnD=r|bA23SctXG3x!#WDpvEx-_oDK=YlEjYb<&sV z@-?pD7jW{^4|ldKw;6T7S`4qk-`3)+75vaGLI|=&K@cs7D!JKuxjWiwVcZ<;u--T! ze^-}F2CZ(iN>s&*{pFrCTcerC*q1D*ml?g;G$LKdA z)s9P)cLXHs9f-L|qs8oc_KRoaeva~)!wMQ9$~8A%`IVn?i>1CSH6G^3mMl6DeE)Sv zubjL^;fsW&sMX)!1ia%w-#dLUOzO zSY`dg_)Ld*{f>{6ay#cv%x@yd?r9%$)Avv|N7PyNu@M@%)VTv9y(tI0o)E8+?lPrNz37Rj2@G zrjta8jg>#t4mR=y4jWWs_hIx!UM?3o0n&NWx zg$lcJ+MKoApS@hTu%dB_rDn44rDm)}dHHYJM}nV7Sk}M)Ks##a`sg-o#}|!&P|Cv# zy*v}!l_ZbOYHz;LoL*M=>%@-gXSi{I0D?roqEViKqeGiKs+60}ZjGPE|x9I0O< zU|Q$IA}!;KqED-tf9{c4n7=Zye9&`*fH24(b%&IZP`Xjz-t(hx`C`Unb5h8>wCHAV zn%4~-^^3GfJ9?O_hqyw-C|B)nh-Y8z^?vfO+wY+YmGO!Lb+r9$e)5`A)dr4|rV>go zG|rS&WgU#~7AqoWN1Yq&pYgo#YRp`#J8*hmefedcna}ct26(~jaK?k{Vk7h?9;M2x zb55NgiIw*?S#DuGDc0O@_3S`*cHc-qOzy%ebdA_%SB?U@MtncXR z;ev(O*#6UX0wA0OF9Cii=wk!9L#~hzWDhw)ILHC=fvkb&4LL&rkPA45+w~m5IUc~r z{*>|mN0SpK7s$mwEZl~<#sLei&7UIjXA8by5X1nK7wA!-$Xym;w!DBx2$-}5mKgAb z0W7)$PGK}+z$y-$;Rv|_w&7l|53>xj3g^rL7W{yG6EGg>?`>e=oa{j1amWJI0D1-} z8JUf{i@TSSjSbcf2a%KSdOBW?t{ATXh>GfGR~h5w43UuFu>QC+j#xj4g5oc?FgS$q z@UU{jxMD$5$-~3I0SlTy2nnfnZ+@5}1RVHD2nY?J6ySe9h6Zg#p#QB<0RT>*-+o_r zEjyaEZwL0_T)?vXMM1khd{4l>h7q&_j}?AKf@AP`u)t$<0QP_=3E{;J zkedd^`kot(?alo?_Rr;p;3Wdijev7~&ke`+=Kdc0Cv(I12Amt1KXb#ey}7@~{;Awl zAOkU+^bdiT)gRBqgrRKNgkT8XK;fvCih zRC*aI##b^uUehMgDbpNOxDgQ;H!+u%$GZj1*ko&!mIwu$@vk33H#bv8RS<_>UF%db;r+#${-1vsCrIIy!+T= zQ<0@2k&xd}A~}?3#x0rlGLUyQ_qd9Swqn)s+>>>SVGsR_2&8sMT=#E^TH2}iKD4i| zoGyQnmh8FIxJ2ByTJ?aR0Na>IOZL%2miZ3pocM4Yx(gpYiwT?~^Yz&v`YUH4XK+UB zfUhe+;k)in&Q7Z683^eEzBJq;6zQVA@NA*dU9)r*XU|-L7J0}uNGsA#gtR6% zg%x?aL)zZ3E1-I8VxDB6J~1aLI}0$ZCT-FUzC25G%qY`b;Z|8V#*lQ*vL=O_@xD@vXztq$# zCMO;wQAr@nV&Y(t5PgTVmdZPUuzbMA{hLnV!~D!}5>{`1GI7c>8_UzWXYJx6G@SGt ztMkJMIMi~^$FK|RzlY#|#`}_v#f6iv-Pr+y#yB`RI5;`FupMf5CLAYhJLl0N^}tiU zf|c2XIg>SrnVd!TG7Bs7nZ=t6%Okqxujrq(l?%s}U{a@^iA#~!R%k1i8tE1FFe)%D z@lW?+Ya>y=H3V5TwB9dyyyZPrWNN# z1X771hUm}qiKGrR-6(6iE9}vDb(@ji<^YGnh9ka#tF;j&vz_GTXHmpf9^kzmF<6FL z>wP@P>pxfYh`7vndNBRS{q!uJN%t4(E{<9E*XvX#9PX)XA1N|#-#X4<$W?01EitC& z=9}=l=1S6345QDWOvf|Udtsov#gNrId(tu}b9H5I31vjl z!^dEM!zgPu@{*vT@r_&J_w{pB-jCE7l?F?$xC|%XliZ0{X!x=it7mia=Bd}k6zMl< z?1&#F;?uh`TIL~0wz#W0LC|jqJ z&SgfM@(ue5aNc{MA`t{bKvf9|2t+7|Nd=R-F1@Q)CB#*6$!wS^%U-pTfc6x47#>IP9 zn6b`aRBxk42t9I(F>%_8Bf3Oj*^kKKw63yWXwu4|uRTMjvpWaT1gMJD<8@a!GBnib zUWxj&k;L8~86*_xY%!$@L_ZxHtVSC;dlin}B$>RmJn4WbzpZ=e3yzS8E}DW6Fu#{V z1Q^#D&#+W7Ik}`@l_cF;qffdHnvXR%E|oGB$Xo>lfX4eOCEUi9m411yD^Dp)X6{kH zS>fQPU@*i-R`p+(z_?eqU?L-~RF0H$n-LczN8VO`x!q)nW8{&e$?SG@HrF%{dN5UA z5Igu3o74T`l9`O#8s1OTdd?v%2~{U162b5U%mpX`Xe>~8a)bdZ++q|O{b5eYZQrqc zY*Y9?^O%^q+QcjKKBsFvA{Inul+bOT12+hC3}WA-5>^W(9_49_$I@)#*-V?eNN4YU zQM4F~u)H?VlC5(&?s(eLa=vb}BvD5(k=A%Fy?AcAT82K29>YCh2}9i|F=tQd`joa3 zS~U{xz~W7{A=KhY+X65Kk^l1-)0G?y6Nn$y>%_O#ug_l${9@o21HTyf#lSBHelhTi zfnN;#V&E48{~!b3pYxS;G&uDg?Kw}lxZ5~$N{GQlDDV;x5Ejg|_Rk4Xpl%Pp6@>E^ zHZ&lG5IiK@7O?vGlR*ST)1W@E`|$&S4FFh5^6mqrUAVjp>e_z-{Pcd&E*2lafprg- zn>)@5yn?h(M}q*A9$ZK4bs@ literal 0 HcmV?d00001 diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex b/jadx-plugins/jadx-dex-input/src/test/resources/samples/hello.dex new file mode 100644 index 0000000000000000000000000000000000000000..226651de12b93d7a8d578914674eeac3de481868 GIT binary patch literal 824 zcmZ9Kzfaph6vvpqSwNg`BscJefImC)Cvd<ejU34Ui8U(_q})T&Udfl#>Hn#%hKFEzq)w8 z_3Lc&l{}i?8hj}oUH^IgVsXV12}LAsHD=)w`(%ndL`1y+z5%4kB{0Ah$bo<9cphR7 zYZ~w;;1qlYU%_{934VjWfO!Ql2}@OuD{E$&!0&U~B(%ttx#lA5@O8J=t!>y4yF2%YC@!#{?m@gWB0iG{xy zb6n3=^m)mzE3KjpnR@AZUQjN52>M<}ipi+ja}FFSG?T%aEHpW;si5|@uXOY->bs7= zCbJ{ib9A@%rrmRQqPrUMlQ-2=X^(Lv4Z_b3L!W2v}_Ir z&sUD@oxl&3=R_*d6+h^>_Nv`++xuO+Z2L-AoKV@T^|F27_V6KpiJ$4cPD7>fPH6O5pE2cjuVwwleqbJZj&Gjz9s7eid(SA1-j@Amr7_>m dm>KEp6B9c3Vt)!^I=;7s9L67cfHY?R-vFYbb(a7D literal 0 HcmV?d00001 diff --git a/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali b/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali new file mode 100644 index 000000000..b19a35b80 --- /dev/null +++ b/jadx-plugins/jadx-dex-input/src/test/resources/samples/test.smali @@ -0,0 +1,16 @@ +.class LHello; +.super Ljava/lang/Object; +.source "test.java" + + +.method public main([Ljava/lang/String;)V + .registers 2 + + .line 3 + nop + + move v0, v1 + + .line 4 + return-void +.end method diff --git a/jadx-plugins/jadx-java-convert/build.gradle b/jadx-plugins/jadx-java-convert/build.gradle new file mode 100644 index 000000000..69b8081dc --- /dev/null +++ b/jadx-plugins/jadx-java-convert/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java-library' +} + +dependencies { + api(project(":jadx-plugins:jadx-plugins-api")) + + implementation(project(":jadx-plugins:jadx-dex-input")) + implementation('com.android.tools:r8:1.6.84') + implementation(files('lib/dx-1.16.jar')) +} diff --git a/jadx-core/lib/dx-1.16.jar b/jadx-plugins/jadx-java-convert/lib/dx-1.16.jar similarity index 100% rename from jadx-core/lib/dx-1.16.jar rename to jadx-plugins/jadx-java-convert/lib/dx-1.16.jar diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java new file mode 100644 index 000000000..c050782d8 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/ConvertResult.java @@ -0,0 +1,67 @@ +package jadx.plugins.input.javaconvert; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConvertResult { + private static final Logger LOG = LoggerFactory.getLogger(ConvertResult.class); + + private final List converted = new ArrayList<>(); + private final List tmpPaths = new ArrayList<>(); + + public List getConverted() { + return converted; + } + + public void addConvertedFiles(List paths) { + converted.addAll(paths); + } + + public void addTempPath(Path path) { + tmpPaths.add(path); + } + + public boolean isEmpty() { + return converted.isEmpty(); + } + + public void deleteTemp() { + for (Path tmpPath : tmpPaths) { + try { + delete(tmpPath); + } catch (Exception e) { + LOG.warn("Failed to delete temp path: {}", tmpPath, e); + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void delete(Path path) throws IOException { + if (Files.isRegularFile(path)) { + Files.delete(path); + return; + } + if (Files.isDirectory(path)) { + try (Stream pathStream = Files.walk(path)) { + pathStream + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + } + + @Override + public String toString() { + return "ConvertResult{converted=" + converted + ", tmpPaths=" + tmpPaths + '}'; + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java new file mode 100644 index 000000000..6ec019c5b --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/D8Converter.java @@ -0,0 +1,53 @@ +package jadx.plugins.input.javaconvert; + +import java.nio.file.Path; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.CompilationMode; +import com.android.tools.r8.D8; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.OutputMode; + +public class D8Converter { + private static final Logger LOG = LoggerFactory.getLogger(D8Converter.class); + + public static void run(Path path, Path tempDirectory) throws CompilationFailedException { + D8Command d8Command = D8Command.builder(new LogHandler()) + .addProgramFiles(path) + .setOutput(tempDirectory, OutputMode.DexIndexed) + .setMode(CompilationMode.DEBUG) + .setMinApiLevel(30) + .setIntermediate(true) + .setEnableDesugaring(false) + .build(); + D8.run(d8Command); + } + + private static class LogHandler implements DiagnosticsHandler { + @Override + public void error(Diagnostic diagnostic) { + LOG.error("D8 error: {}", format(diagnostic)); + } + + @Override + public void warning(Diagnostic diagnostic) { + LOG.warn("D8 warning: {}", format(diagnostic)); + } + + @Override + public void info(Diagnostic diagnostic) { + LOG.info("D8 info: {}", format(diagnostic)); + } + + public static String format(Diagnostic diagnostic) { + return diagnostic.getDiagnosticMessage() + + ", origin: " + diagnostic.getOrigin() + + ", position: " + diagnostic.getPosition(); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java new file mode 100644 index 000000000..8a9be16d9 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/DxConverter.java @@ -0,0 +1,49 @@ +package jadx.plugins.input.javaconvert; + +import java.io.ByteArrayOutputStream; +import java.nio.file.Path; + +import com.android.dx.command.dexer.DxContext; +import com.android.dx.command.dexer.Main; + +public class DxConverter { + private static final String CHARSET_NAME = "UTF-8"; + + private static class DxArgs extends com.android.dx.command.dexer.Main.Arguments { + public DxArgs(DxContext context, String dexDir, String[] input) { + super(context); + outName = dexDir; + fileNames = input; + jarOutput = false; + multiDex = true; + + optimize = true; + localInfo = true; + coreLibrary = true; + + debug = true; + warnings = true; + minSdkVersion = 28; + } + } + + public static void run(Path path, Path tempDirectory) { + int result; + String dxErrors; + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream errOut = new ByteArrayOutputStream()) { + DxContext context = new DxContext(out, errOut); + DxArgs args = new DxArgs( + context, + tempDirectory.toAbsolutePath().toString(), + new String[] { path.toAbsolutePath().toString() }); + result = new Main(context).runDx(args); + dxErrors = errOut.toString(CHARSET_NAME); + } catch (Exception e) { + throw new RuntimeException("dx exception: " + e.getMessage(), e); + } + if (result != 0) { + throw new RuntimeException("Java to dex conversion error, code: " + result + "\n errors: " + dxErrors); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java new file mode 100644 index 000000000..0db886ea7 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertLoader.java @@ -0,0 +1,52 @@ +package jadx.plugins.input.javaconvert; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JavaConvertLoader { + private static final Logger LOG = LoggerFactory.getLogger(JavaConvertLoader.class); + + public static ConvertResult process(List input) { + ConvertResult result = new ConvertResult(); + for (Path path : input) { + if (isJavaFile(path)) { + try { + convert(result, path); + } catch (Exception e) { + LOG.error("Failed to convert file: " + path.toAbsolutePath(), e); + } + } + } + return result; + } + + private static boolean isJavaFile(Path path) { + String fileName = path.getFileName().toString(); + return fileName.endsWith(".jar") + || fileName.endsWith(".class"); + } + + private static void convert(ConvertResult result, Path path) throws Exception { + Path tempDirectory = Files.createTempDirectory("jadx-"); + result.addTempPath(tempDirectory); + + // D8Converter.run(path, tempDirectory); + DxConverter.run(path, tempDirectory); + + LOG.debug("Converted to dex: {}", path.toAbsolutePath()); + result.addConvertedFiles(collectFilesInDir(tempDirectory)); + } + + private static List collectFilesInDir(Path tempDirectory) throws IOException { + try (Stream pathStream = Files.walk(tempDirectory)) { + return pathStream.collect(Collectors.toList()); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java new file mode 100644 index 000000000..8227a11ad --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java @@ -0,0 +1,38 @@ +package jadx.plugins.input.javaconvert; + +import java.io.IOException; +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; +import jadx.plugins.input.dex.DexFileLoader; +import jadx.plugins.input.dex.DexLoadResult; +import jadx.plugins.input.dex.DexReader; + +public class JavaConvertPlugin implements JadxInputPlugin { + + @Override + public JadxPluginInfo getPluginInfo() { + return new JadxPluginInfo("java-convert", "JavaConvert", "Convert .jar and .class files to dex"); + } + + @Override + public ILoadResult loadFiles(List input) { + ConvertResult result = JavaConvertLoader.process(input); + if (result.isEmpty()) { + result.deleteTemp(); + return EmptyLoadResult.INSTANCE; + } + List dexReaders = DexFileLoader.collectDexFiles(result.getConverted()); + return new DexLoadResult(dexReaders) { + @Override + public void close() throws IOException { + super.close(); + result.deleteTemp(); + } + }; + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..df9405ff5 --- /dev/null +++ b/jadx-plugins/jadx-java-convert/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.input.javaconvert.JavaConvertPlugin diff --git a/jadx-plugins/jadx-plugins-api/build.gradle b/jadx-plugins/jadx-plugins-api/build.gradle new file mode 100644 index 000000000..97734ac60 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'java-library' +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java new file mode 100644 index 000000000..51337098b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPlugin.java @@ -0,0 +1,5 @@ +package jadx.api.plugins; + +public interface JadxPlugin { + JadxPluginInfo getPluginInfo(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java new file mode 100644 index 000000000..e9670f72b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginInfo.java @@ -0,0 +1,30 @@ +package jadx.api.plugins; + +public class JadxPluginInfo { + private final String pluginId; + private final String name; + private final String description; + + public JadxPluginInfo(String id, String name, String description) { + this.pluginId = id; + this.name = name; + this.description = description; + } + + public String getPluginId() { + return pluginId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return name + " - '" + description + '\''; + } +} 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 new file mode 100644 index 000000000..ba2f7bcab --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/JadxPluginManager.java @@ -0,0 +1,44 @@ +package jadx.api.plugins; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.JadxInputPlugin; + +public class JadxPluginManager { + private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class); + + private final Map, JadxPlugin> allPlugins = new HashMap<>(); + + public JadxPluginManager() { + ServiceLoader jadxPlugins = ServiceLoader.load(JadxPlugin.class); + for (JadxPlugin jadxPlugin : jadxPlugins) { + register(jadxPlugin); + } + } + + public void register(JadxPlugin plugin) { + Objects.requireNonNull(plugin); + LOG.debug("Loaded plugin: {}", plugin.getPluginInfo().getName()); + allPlugins.put(plugin.getClass(), plugin); + } + + public List getAllPlugins() { + return new ArrayList<>(allPlugins.values()); + } + + public List getInputPlugins() { + return allPlugins.values().stream() + .filter(JadxInputPlugin.class::isInstance) + .map(JadxInputPlugin.class::cast) + .collect(Collectors.toList()); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java new file mode 100644 index 000000000..6f07f451f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/JadxInputPlugin.java @@ -0,0 +1,11 @@ +package jadx.api.plugins.input; + +import java.nio.file.Path; +import java.util.List; + +import jadx.api.plugins.JadxPlugin; +import jadx.api.plugins.input.data.ILoadResult; + +public interface JadxInputPlugin extends JadxPlugin { + ILoadResult loadFiles(List input); +} 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 new file mode 100644 index 000000000..9f01c2c2b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlags.java @@ -0,0 +1,91 @@ +package jadx.api.plugins.input.data; + +public class AccessFlags { + public static final int PUBLIC = 0x1; + public static final int PRIVATE = 0x2; + public static final int PROTECTED = 0x4; + public static final int STATIC = 0x8; + public static final int FINAL = 0x10; + public static final int SYNCHRONIZED = 0x20; + public static final int SUPER = 0x20; + public static final int VOLATILE = 0x40; + public static final int BRIDGE = 0x40; + public static final int TRANSIENT = 0x80; + public static final int VARARGS = 0x80; + public static final int NATIVE = 0x100; + public static final int INTERFACE = 0x200; + public static final int ABSTRACT = 0x400; + public static final int STRICT = 0x800; + public static final int SYNTHETIC = 0x1000; + public static final int ANNOTATION = 0x2000; + public static final int ENUM = 0x4000; + public static final int CONSTRUCTOR = 0x10000; + public static final int DECLARED_SYNCHRONIZED = 0x20000; + + public static boolean hasFlag(int flags, int flagValue) { + return (flags & flagValue) != 0; + } + + public static String format(int flags, AccessFlagsScope scope) { + StringBuilder code = new StringBuilder(); + if (hasFlag(flags, PUBLIC)) { + code.append("public "); + } + if (hasFlag(flags, PRIVATE)) { + code.append("private "); + } + if (hasFlag(flags, PROTECTED)) { + code.append("protected "); + } + if (hasFlag(flags, STATIC)) { + code.append("static "); + } + if (hasFlag(flags, FINAL)) { + code.append("final "); + } + if (hasFlag(flags, ABSTRACT)) { + code.append("abstract "); + } + if (hasFlag(flags, NATIVE)) { + code.append("native "); + } + switch (scope) { + case METHOD: + if (hasFlag(flags, SYNCHRONIZED)) { + code.append("synchronized "); + } + if (hasFlag(flags, BRIDGE)) { + code.append("bridge "); + } + if (hasFlag(flags, VARARGS)) { + code.append("varargs "); + } + break; + + case FIELD: + if (hasFlag(flags, VOLATILE)) { + code.append("volatile "); + } + if (hasFlag(flags, TRANSIENT)) { + code.append("transient "); + } + break; + + case CLASS: + if (hasFlag(flags, STRICT)) { + code.append("strict "); + } + if (hasFlag(flags, SUPER)) { + code.append("super "); + } + if (hasFlag(flags, ENUM)) { + code.append("enum "); + } + break; + } + if (hasFlag(flags, SYNTHETIC)) { + code.append("synthetic "); + } + return code.toString(); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java new file mode 100644 index 000000000..6a95fda8e --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/AccessFlagsScope.java @@ -0,0 +1,5 @@ +package jadx.api.plugins.input.data; + +public enum AccessFlagsScope { + CLASS, FIELD, METHOD +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java new file mode 100644 index 000000000..c4fb65851 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data; + +public interface ICatch { + String[] getTypes(); + + int[] getAddresses(); + + int getCatchAllAddress(); +} 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 new file mode 100644 index 000000000..f35757fa0 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IClassData.java @@ -0,0 +1,35 @@ +package jadx.api.plugins.input.data; + +import java.nio.file.Path; +import java.util.List; +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; + +public interface IClassData { + + String getType(); + + int getAccessFlags(); + + @Nullable + String getSuperType(); + + List getInterfacesTypes(); + + String getSourceFile(); + + Path getInputPath(); + + void visitFieldsAndMethods(Consumer fieldsConsumer, Consumer mthConsumer); + + List getStaticFieldInitValues(); + + List getAnnotations(); + + // TODO: make api methods to get dissembled code + int getClassDefOffset(); +} 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 new file mode 100644 index 000000000..bf300b569 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICodeReader.java @@ -0,0 +1,25 @@ +package jadx.api.plugins.input.data; + +import java.util.List; +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.insns.InsnData; + +public interface ICodeReader { + ICodeReader copy(); + + void visitInstructions(Consumer insnConsumer); + + int getRegistersCount(); + + int getInsnsCount(); + + @Nullable + IDebugInfo getDebugInfo(); + + int getCodeOffset(); + + List getTries(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java new file mode 100644 index 000000000..78aea095a --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IDebugInfo.java @@ -0,0 +1,14 @@ +package jadx.api.plugins.input.data; + +import java.util.List; +import java.util.Map; + +public interface IDebugInfo { + + /** + * Map instruction offset to source line number + */ + Map getSourceLineMapping(); + + List getLocalVars(); +} 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 new file mode 100644 index 000000000..99c59d74d --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IFieldData.java @@ -0,0 +1,17 @@ +package jadx.api.plugins.input.data; + +import java.util.List; + +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public interface IFieldData { + String getParentClassType(); + + String getType(); + + String getName(); + + int getAccessFlags(); + + List getAnnotations(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java new file mode 100644 index 000000000..932afa7c5 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILoadResult.java @@ -0,0 +1,10 @@ +package jadx.api.plugins.input.data; + +import java.io.Closeable; +import java.util.function.Consumer; + +public interface ILoadResult extends Closeable { + void visitClasses(Consumer consumer); + + void visitResources(Consumer consumer); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java new file mode 100644 index 000000000..24a52b283 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ILocalVar.java @@ -0,0 +1,18 @@ +package jadx.api.plugins.input.data; + +import org.jetbrains.annotations.Nullable; + +public interface ILocalVar { + String getName(); + + int getRegNum(); + + String getType(); + + @Nullable + String getSignature(); + + int getStartOffset(); + + int getEndOffset(); +} 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 new file mode 100644 index 000000000..d942f6147 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IMethodData.java @@ -0,0 +1,30 @@ +package jadx.api.plugins.input.data; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.IAnnotation; + +public interface IMethodData { + String getParentClassType(); + + String getName(); + + int getAccessFlags(); + + String getReturnType(); + + List getArgTypes(); + + boolean isDirect(); + + @Nullable + ICodeReader getCodeReader(); + + String disassembleMethod(); + + List getAnnotations(); + + List> getParamsAnnotations(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java new file mode 100644 index 000000000..2fd81769f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/IResourceData.java @@ -0,0 +1,4 @@ +package jadx.api.plugins.input.data; + +public interface IResourceData { +} 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 new file mode 100644 index 000000000..85f54ff42 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.data; + +public interface ITry { + ICatch getCatch(); + + int getStartAddress(); + + int getInstructionCount(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java new file mode 100644 index 000000000..95d96e9cb --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/AnnotationVisibility.java @@ -0,0 +1,7 @@ +package jadx.api.plugins.input.data.annotations; + +public enum AnnotationVisibility { + BUILD, + RUNTIME, + SYSTEM +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java new file mode 100644 index 000000000..0a773a682 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedType.java @@ -0,0 +1,20 @@ +package jadx.api.plugins.input.data.annotations; + +public enum EncodedType { + ENCODED_NULL, + ENCODED_BOOLEAN, + ENCODED_BYTE, + ENCODED_SHORT, + ENCODED_CHAR, + ENCODED_INT, + ENCODED_LONG, + ENCODED_FLOAT, + ENCODED_DOUBLE, + ENCODED_STRING, + ENCODED_TYPE, + ENCODED_ENUM, + ENCODED_FIELD, + ENCODED_METHOD, + ENCODED_ARRAY, + ENCODED_ANNOTATION +} 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 new file mode 100644 index 000000000..78b4188d4 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java @@ -0,0 +1,33 @@ +package jadx.api.plugins.input.data.annotations; + +public class EncodedValue { + public static final EncodedValue NULL = new EncodedValue(EncodedType.ENCODED_NULL, new Object()); + + private final EncodedType type; + private final Object value; + + public EncodedValue(EncodedType type, Object value) { + this.type = type; + this.value = value; + } + + public EncodedType getType() { + return type; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + switch (type) { + case ENCODED_STRING: + return (String) value; + case ENCODED_ARRAY: + return "[" + value + "]"; + default: + return "{" + type + ": " + value + '}'; + } + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java new file mode 100644 index 000000000..5d0a702d4 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java @@ -0,0 +1,18 @@ +package jadx.api.plugins.input.data.annotations; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +public interface IAnnotation { + String getAnnotationClass(); + + AnnotationVisibility getVisibility(); + + Map getValues(); + + @Nullable + default EncodedValue getDefaultValue() { + return getValues().get("value"); + } +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java new file mode 100644 index 000000000..94fb0e74f --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/EmptyLoadResult.java @@ -0,0 +1,25 @@ +package jadx.api.plugins.input.data.impl; + +import java.io.IOException; +import java.util.function.Consumer; + +import jadx.api.plugins.input.data.IClassData; +import jadx.api.plugins.input.data.ILoadResult; +import jadx.api.plugins.input.data.IResourceData; + +public class EmptyLoadResult implements ILoadResult { + + public static final EmptyLoadResult INSTANCE = new EmptyLoadResult(); + + @Override + public void visitClasses(Consumer consumer) { + } + + @Override + public void visitResources(Consumer consumer) { + } + + @Override + public void close() throws IOException { + } +} 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 new file mode 100644 index 000000000..dad3ae8f6 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnData.java @@ -0,0 +1,41 @@ +package jadx.api.plugins.input.insns; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.custom.ICustomPayload; + +public interface InsnData { + + void decode(); + + int getOffset(); + + Opcode getOpcode(); + + InsnIndexType getIndexType(); + + int getRawOpcodeUnit(); + + int getRegsCount(); + + int getReg(int argNum); + + long getLiteral(); + + int getTarget(); + + int getIndex(); + + String getIndexAsString(); + + String getIndexAsType(); + + IFieldData getIndexAsField(); + + IMethodData getIndexAsMethod(); + + @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 new file mode 100644 index 000000000..1153c796b --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/InsnIndexType.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns; + +public enum InsnIndexType { + NONE, + TYPE_REF, + STRING_REF, + FIELD_REF, + METHOD_REF +} 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 new file mode 100644 index 000000000..003c27310 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/Opcode.java @@ -0,0 +1,193 @@ +package jadx.api.plugins.input.insns; + +public enum Opcode { + UNKNOWN, + NOP, + + ADD_DOUBLE, + ADD_FLOAT, + ADD_INT, + ADD_INT_LIT, + ADD_LONG, + + AND_INT, + AND_INT_LIT, + AND_LONG, + + AGET, + AGET_BOOLEAN, + AGET_BYTE, + AGET_CHAR, + AGET_OBJECT, + AGET_SHORT, + AGET_WIDE, + + APUT, + APUT_BOOLEAN, + APUT_BYTE, + APUT_CHAR, + APUT_OBJECT, + APUT_SHORT, + APUT_WIDE, + + ARITH, + ARRAY_LENGTH, + + CAST, + CHECK_CAST, + + CMPG_DOUBLE, + CMPG_FLOAT, + CMPL_DOUBLE, + CMPL_FLOAT, + CMP_G, + CMP_L, + CMP_LONG, + + CONST, + CONST_CLASS, + CONST_STRING, + CONST_WIDE, + + DIV_DOUBLE, + DIV_FLOAT, + DIV_INT, + DIV_INT_LIT, + DIV_LONG, + + DOUBLE_TO_FLOAT, + DOUBLE_TO_INT, + DOUBLE_TO_LONG, + + FLOAT_TO_DOUBLE, + FLOAT_TO_INT, + FLOAT_TO_LONG, + + GOTO, + IF, + IF_EQ, + IF_EQZ, + IF_GE, + IF_GEZ, + IF_GT, + IF_GTZ, + IF_LE, + IF_LEZ, + IF_LT, + IF_LTZ, + IF_NE, + IF_NEZ, + + INSTANCE_OF, + + INT_TO_BYTE, + INT_TO_CHAR, + INT_TO_DOUBLE, + INT_TO_FLOAT, + INT_TO_LONG, + INT_TO_SHORT, + + INVOKE_DIRECT, + INVOKE_DIRECT_RANGE, + INVOKE_INTERFACE, + INVOKE_INTERFACE_RANGE, + INVOKE_STATIC, + INVOKE_STATIC_RANGE, + INVOKE_SUPER, + INVOKE_SUPER_RANGE, + INVOKE_VIRTUAL, + INVOKE_VIRTUAL_RANGE, + + IGET, + IPUT, + + SGET, + SPUT, + + LONG_TO_DOUBLE, + LONG_TO_FLOAT, + LONG_TO_INT, + + MONITOR_ENTER, + MONITOR_EXIT, + + MOVE, + MOVE_EXCEPTION, + MOVE_OBJECT, + MOVE_RESULT, + MOVE_WIDE, + + MUL_DOUBLE, + MUL_FLOAT, + MUL_INT, + MUL_INT_LIT, + MUL_LONG, + + NEG, + NEG_DOUBLE, + NEG_FLOAT, + NEG_INT, + NEG_LONG, + NEW_INSTANCE, + + NOT, + NOT_INT, + NOT_LONG, + + OR_INT, + OR_INT_LIT, + OR_LONG, + + REM_DOUBLE, + REM_FLOAT, + REM_INT, + REM_INT_LIT, + REM_LONG, + + RETURN, + RETURN_VOID, + + RSUB_INT, + SHL_INT, + SHL_INT_LIT, + SHL_LONG, + SHR_INT, + SHR_INT_LIT, + SHR_LONG, + SUB_DOUBLE, + SUB_FLOAT, + SUB_INT, + SUB_LONG, + + THROW, + + USHR_INT, + USHR_INT_LIT, + USHR_LONG, + XOR_INT, + XOR_INT_LIT, + XOR_LONG, + + NEW_ARRAY, + + FILLED_NEW_ARRAY, + FILLED_NEW_ARRAY_RANGE, + FILL_ARRAY, + FILL_ARRAY_DATA, + FILL_ARRAY_DATA_PAYLOAD, + + SWITCH, + PACKED_SWITCH, + PACKED_SWITCH_PAYLOAD, + SPARSE_SWITCH, + SPARSE_SWITCH_PAYLOAD, + + INVOKE_POLYMORPHIC, + INVOKE_POLYMORPHIC_RANGE, + + INVOKE_CUSTOM, + INVOKE_CUSTOM_RANGE, + + CONST_METHOD_HANDLE, + CONST_METHOD_TYPE, +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java new file mode 100644 index 000000000..c4d66bd1d --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/IArrayPayload.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns.custom; + +public interface IArrayPayload extends ICustomPayload { + int getSize(); + + int getElementSize(); + + Object getData(); +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java new file mode 100644 index 000000000..2306a6ff2 --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ICustomPayload.java @@ -0,0 +1,4 @@ +package jadx.api.plugins.input.insns.custom; + +public interface ICustomPayload { +} diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java new file mode 100644 index 000000000..56513855a --- /dev/null +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/insns/custom/ISwitchPayload.java @@ -0,0 +1,9 @@ +package jadx.api.plugins.input.insns.custom; + +public interface ISwitchPayload extends ICustomPayload { + int getSize(); + + int[] getKeys(); + + int[] getTargets(); +} diff --git a/settings.gradle b/settings.gradle index cde32274a..17e164d65 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,7 @@ include 'jadx-core' include 'jadx-cli' include 'jadx-gui' include 'jadx-samples' +include 'jadx-plugins' +include 'jadx-plugins:jadx-plugins-api' +include 'jadx-plugins:jadx-dex-input' +include 'jadx-plugins:jadx-java-convert' From d720179debbb27c7deadbfcb231bcade9176f966 Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 21 May 2020 21:56:58 +0100 Subject: [PATCH 42/55] fix: collect class usage and fix class access modifiers (#729) --- .../main/java/jadx/api/JadxDecompiler.java | 1 + jadx-core/src/main/java/jadx/core/Jadx.java | 11 +- .../src/main/java/jadx/core/ProcessClass.java | 2 +- .../java/jadx/core/dex/nodes/ClassNode.java | 22 ++- .../java/jadx/core/dex/nodes/RootNode.java | 18 +++ .../dex/visitors/DependencyCollector.java | 144 ++++++++++++------ .../core/dex/visitors/FixAccessModifiers.java | 46 +++++- .../functional/JadxVisitorsOrderTest.java | 10 +- 8 files changed, 193 insertions(+), 61 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 65e1c8bd9..4e78b4308 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -98,6 +98,7 @@ public final class JadxDecompiler implements Closeable { root.initClassPath(); root.loadResources(getResources()); root.initPasses(); + root.runPreDecompileStage(); } private void loadInputFiles() { diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 51846927a..84889304f 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -76,6 +76,13 @@ public class Jadx { return passes; } + public static List getPreDecompilePassesList() { + List passes = new ArrayList<>(); + passes.add(new RenameVisitor()); + passes.add(new DependencyCollector()); + return passes; + } + public static List getPassesList(JadxArgs args) { if (args.isFallbackMode()) { return getFallbackPassesList(); @@ -146,10 +153,6 @@ public class Jadx { if (args.isCfgOutput()) { passes.add(DotGraphVisitor.dumpRegions()); } - - passes.add(new DependencyCollector()); - passes.add(new RenameVisitor()); - return passes; } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 35e82ebc3..5e0bad411 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -54,8 +54,8 @@ public final class ProcessClass { return generateCode(topParentClass); } try { - process(cls); cls.getDependencies().forEach(ProcessClass::process); + process(cls); ICodeInfo code = CodeGen.generate(cls); cls.unload(); 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 ca1516337..0a7ad5321 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 @@ -12,6 +12,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +44,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; -public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode { +public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final RootNode root; @@ -69,6 +70,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN private volatile ProcessState state = ProcessState.NOT_LOADED; private List dependencies = Collections.emptyList(); + private List usedIn = Collections.emptyList(); // cache maps private Map mthInfoMap = Collections.emptyMap(); @@ -478,6 +480,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return contains(AFlag.ANONYMOUS_CLASS); } + public boolean isInner() { + return parentClass != null; + } + @Nullable public MethodNode getClassInitMth() { return searchMethodByShortId("()V"); @@ -579,6 +585,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } + public List getUsedIn() { + return usedIn; + } + + public void setUsedIn(List usedIn) { + this.usedIn = usedIn; + } + @Override public Path getInputPath() { return inputPath; @@ -601,9 +615,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return false; } + @Override + public int compareTo(@NotNull ClassNode o) { + return this.getFullName().compareTo(o.getFullName()); + } + @Override public String toString() { return clsInfo.getFullName(); } - } 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 10ddbdac1..ee95b0297 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 @@ -28,6 +28,7 @@ import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.utils.MethodUtils; import jadx.core.dex.nodes.utils.TypeUtils; +import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.typeinference.TypeUpdate; import jadx.core.utils.CacheStorage; @@ -189,10 +190,27 @@ public class RootNode { } } + public void runPreDecompileStage() { + for (IDexTreeVisitor pass : Jadx.getPreDecompilePassesList()) { + try { + pass.init(this); + } catch (Exception e) { + LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e); + } + for (ClassNode cls : classes) { + DepthTraversal.visit(pass, cls); + } + } + } + public List getClasses() { return classes; } + public List getClassesWithoutInner() { + return getClasses(false); + } + public List getClasses(boolean includeInner) { if (includeInner) { return classes; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java index 0e4b4b4c7..9c39c31f5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java @@ -1,33 +1,61 @@ package jadx.core.dex.visitors; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.data.IFieldData; +import jadx.api.plugins.input.data.IMethodData; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.Opcode; import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.FieldInitAttr; import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.info.FieldInfo; -import jadx.core.dex.instructions.BaseInvokeNode; -import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.instructions.args.InsnWrapArg; -import jadx.core.dex.instructions.args.RegisterArg; -import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.exceptions.JadxException; +@JadxVisitor( + name = "DependencyCollector", + desc = "Scan class and methods and collect dependant classes", + runAfter = { + RenameVisitor.class // sort by alias name + } +) +// TODO: store usage info for fields, methods and inner classes public class DependencyCollector extends AbstractVisitor { @Override - public boolean visit(ClassNode cls) throws JadxException { + public void init(RootNode root) { + List clsList = root.getClassesWithoutInner(); + for (ClassNode cls : clsList) { + collectClassDeps(cls); + } + buildUsageList(clsList); + } + + private void buildUsageList(List clsList) { + clsList.forEach(cls -> cls.setUsedIn(new ArrayList<>())); + for (ClassNode cls : clsList) { + for (ClassNode depCls : cls.getDependencies()) { + depCls.getUsedIn().add(cls); + } + } + for (ClassNode cls : clsList) { + List usedIn = cls.getUsedIn(); + if (usedIn.isEmpty()) { + cls.setUsedIn(Collections.emptyList()); + } else { + Collections.sort(usedIn); + } + } + } + + public void collectClassDeps(ClassNode cls) { RootNode root = cls.root(); Set depSet = new HashSet<>(); processClass(cls, root, depSet); @@ -36,10 +64,13 @@ public class DependencyCollector extends AbstractVisitor { } depSet.remove(cls); - List depList = new ArrayList<>(depSet); - depList.sort(Comparator.comparing(c -> c.getClassInfo().getFullName())); - cls.setDependencies(depList); - return false; + if (depSet.isEmpty()) { + cls.setDependencies(Collections.emptyList()); + } else { + List depList = new ArrayList<>(depSet); + Collections.sort(depList); + cls.setDependencies(depList); + } } private static void processClass(ClassNode cls, RootNode root, Set depList) { @@ -49,12 +80,6 @@ public class DependencyCollector extends AbstractVisitor { } for (FieldNode fieldNode : cls.getFields()) { addDep(root, depList, fieldNode.getType()); - - // process instructions from field init - FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT); - if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) { - processInsn(root, depList, fieldInitAttr.getInsn()); - } } // TODO: process annotations and generics for (MethodNode methodNode : cls.getMethods()) { @@ -71,41 +96,62 @@ public class DependencyCollector extends AbstractVisitor { for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) { addDep(root, depList, arg); } - for (BlockNode block : methodNode.getBasicBlocks()) { - for (InsnNode insnNode : block.getInstructions()) { - processInsn(root, depList, insnNode); - } + try { + processInstructions(methodNode, depList); + } catch (Exception e) { + methodNode.getCodeReader().visitInstructions(insnData -> { + insnData.decode(); + System.out.println(insnData); + }); + methodNode.addError("Dependency scan failed", e); } } - // TODO: add custom instructions processing - private static void processInsn(RootNode root, Set depList, InsnNode insnNode) { - RegisterArg result = insnNode.getResult(); - if (result != null) { - addDep(root, depList, result.getType()); + private static void processInstructions(MethodNode mth, Set deps) { + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader == null) { + return; } - for (InsnArg arg : insnNode.getArguments()) { - if (arg.isInsnWrap()) { - processInsn(root, depList, ((InsnWrapArg) arg).getWrapInsn()); - } else { - addDep(root, depList, arg.getType()); + RootNode root = mth.root(); + codeReader.visitInstructions(insnData -> { + try { + processInsn(root, insnData, deps); + } catch (Exception e) { + mth.addError("Dependency scan failed at insn: " + insnData, e); } - } - processCustomInsn(root, depList, insnNode); + }); } - private static void processCustomInsn(RootNode root, Set depList, InsnNode insn) { - if (insn instanceof IndexInsnNode) { - Object index = ((IndexInsnNode) insn).getIndex(); - if (index instanceof FieldInfo) { - addDep(root, depList, ((FieldInfo) index).getDeclClass()); - } else if (index instanceof ArgType) { - addDep(root, depList, (ArgType) index); - } - } else if (insn instanceof BaseInvokeNode) { - ClassInfo declClass = ((BaseInvokeNode) insn).getCallMth().getDeclClass(); - addDep(root, depList, declClass); + private static void processInsn(RootNode root, InsnData insnData, Set deps) { + if (insnData.getOpcode() == Opcode.UNKNOWN) { + return; } + switch (insnData.getIndexType()) { + case TYPE_REF: + insnData.decode(); + resolveType(root, deps, insnData.getIndexAsType()); + break; + case FIELD_REF: + insnData.decode(); + resolveField(root, deps, insnData.getIndexAsField()); + break; + case METHOD_REF: + insnData.decode(); + resolveMethod(root, deps, insnData.getIndexAsMethod()); + break; + } + } + + private static void resolveType(RootNode root, Set deps, String type) { + addDep(root, deps, ArgType.parse(type)); + } + + private static void resolveMethod(RootNode root, Set deps, IMethodData method) { + resolveType(root, deps, method.getParentClassType()); + } + + private static void resolveField(RootNode root, Set deps, IFieldData field) { + resolveType(root, deps, field.getParentClassType()); } private static void addDep(RootNode root, Set depList, ArgType type) { 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 322350f18..a7166be8a 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 @@ -3,9 +3,11 @@ package jadx.core.dex.visitors; import jadx.api.plugins.input.data.AccessFlags; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ICodeNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.exceptions.JadxException; @JadxVisitor( name = "FixAccessModifiers", @@ -21,12 +23,24 @@ public class FixAccessModifiers extends AbstractVisitor { this.respectAccessModifiers = root.getArgs().isRespectBytecodeAccModifiers(); } + @Override + public boolean visit(ClassNode cls) throws JadxException { + if (respectAccessModifiers) { + return true; + } + int newVisFlag = fixClassVisibility(cls); + if (newVisFlag != -1) { + changeVisibility(cls, newVisFlag); + } + return true; + } + @Override public void visit(MethodNode mth) { if (respectAccessModifiers) { return; } - int newVisFlag = fixVisibility(mth); + int newVisFlag = fixMethodVisibility(mth); if (newVisFlag != -1) { changeVisibility(mth, newVisFlag); } @@ -41,7 +55,35 @@ public class FixAccessModifiers extends AbstractVisitor { } } - private static int fixVisibility(MethodNode mth) { + private int fixClassVisibility(ClassNode cls) { + if (cls.getUsedIn().isEmpty()) { + return -1; + } + AccessInfo accessFlags = cls.getAccessFlags(); + if (accessFlags.isPrivate()) { + if (!cls.isInner()) { + return AccessFlags.PUBLIC; + } + // check if private inner class is used outside + ClassNode topParentClass = cls.getTopParentClass(); + for (ClassNode useCls : cls.getUsedIn()) { + if (useCls.getTopParentClass() != topParentClass) { + return AccessFlags.PUBLIC; + } + } + } + if (accessFlags.isPackagePrivate()) { + String pkg = cls.getPackage(); + for (ClassNode useCls : cls.getUsedIn()) { + if (!useCls.getPackage().equals(pkg)) { + return AccessFlags.PUBLIC; + } + } + } + return -1; + } + + private static int fixMethodVisibility(MethodNode mth) { if (mth.isVirtual()) { // make virtual methods public return AccessFlags.PUBLIC; diff --git a/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java b/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java index afeeb9b01..b6b15e7a7 100644 --- a/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java +++ b/jadx-core/src/test/java/jadx/tests/functional/JadxVisitorsOrderTest.java @@ -18,13 +18,16 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; public class JadxVisitorsOrderTest { - private static final Logger LOG = LoggerFactory.getLogger(JadxVisitorsOrderTest.class); @Test public void testOrder() { - List passes = Jadx.getPassesList(new JadxArgs()); + checkPassList(Jadx.getPassesList(new JadxArgs())); + checkPassList(Jadx.getPreDecompilePassesList()); + checkPassList(Jadx.getFallbackPassesList()); + } + private void checkPassList(List passes) { List errors = check(passes); for (String str : errors) { LOG.error(str); @@ -55,7 +58,8 @@ public class JadxVisitorsOrderTest { errors.add("Visitor name conflict: " + passName + ", class: " + passClass.getName()); } for (Class cls : info.runBefore()) { - if (classList.indexOf(cls) < i) { + int beforeIndex = classList.indexOf(cls); + if (beforeIndex != -1 && beforeIndex < i) { errors.add("Pass " + passName + " must be before " + cls.getSimpleName()); } } From 15776c4ce30be9ab262b035a36402ce25facfe3f Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 24 May 2020 20:30:39 +0100 Subject: [PATCH 43/55] fix: make class public if some method going to be inlined (#729) --- jadx-core/src/main/java/jadx/core/Consts.java | 1 + jadx-core/src/main/java/jadx/core/Jadx.java | 4 +- .../src/main/java/jadx/core/ProcessClass.java | 6 + .../main/java/jadx/core/codegen/ClassGen.java | 46 ++++- .../java/jadx/core/codegen/MethodGen.java | 3 + .../java/jadx/core/dex/nodes/ClassNode.java | 25 ++- .../java/jadx/core/dex/nodes/FieldNode.java | 12 ++ .../java/jadx/core/dex/nodes/MethodNode.java | 17 +- .../jadx/core/dex/nodes/ProcessState.java | 9 +- .../dex/visitors/DependencyCollector.java | 186 ------------------ .../core/dex/visitors/FixAccessModifiers.java | 15 +- .../dex/visitors/MethodInlineVisitor.java | 15 +- .../core/dex/visitors/usage/UsageInfo.java | 101 ++++++++++ .../dex/visitors/usage/UsageInfoVisitor.java | 115 +++++++++++ .../jadx/core/dex/visitors/usage/UseSet.java | 30 +++ .../test/java/jadx/tests/api/SmaliTest.java | 1 + .../api/utils/assertj/JadxAssertions.java | 2 + .../integration/inline/TestMethodInline.java | 55 ++++++ .../others/TestFixClassAccessModifiers.java | 31 +++ .../smali/inline/TestMethodInline/A.smali | 10 + .../smali/inline/TestMethodInline/B.smali | 10 + .../smali/inline/TestMethodInline/C.smali | 8 + .../TestFixClassAccessModifiers/Cls.smali | 8 + .../InnerCls.smali | 11 ++ .../TestFixClassAccessModifiers/TestCls.smali | 4 + 25 files changed, 509 insertions(+), 216 deletions(-) delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/usage/UseSet.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/inline/TestMethodInline.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/others/TestFixClassAccessModifiers.java create mode 100644 jadx-core/src/test/smali/inline/TestMethodInline/A.smali create mode 100644 jadx-core/src/test/smali/inline/TestMethodInline/B.smali create mode 100644 jadx-core/src/test/smali/inline/TestMethodInline/C.smali create mode 100644 jadx-core/src/test/smali/others/TestFixClassAccessModifiers/Cls.smali create mode 100644 jadx-core/src/test/smali/others/TestFixClassAccessModifiers/InnerCls.smali create mode 100644 jadx-core/src/test/smali/others/TestFixClassAccessModifiers/TestCls.smali diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index efe038512..f76c23b93 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -2,6 +2,7 @@ package jadx.core; public class Consts { public static final boolean DEBUG = false; + public static final boolean DEBUG_USAGE = false; public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_STRING = "java.lang.String"; diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 84889304f..83cbfa589 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -17,7 +17,6 @@ import jadx.core.dex.visitors.ClassModifier; import jadx.core.dex.visitors.ConstInlineVisitor; import jadx.core.dex.visitors.ConstructorVisitor; import jadx.core.dex.visitors.DeboxingVisitor; -import jadx.core.dex.visitors.DependencyCollector; import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.EnumVisitor; import jadx.core.dex.visitors.ExtractFieldInit; @@ -55,6 +54,7 @@ import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; +import jadx.core.dex.visitors.usage.UsageInfoVisitor; public class Jadx { private static final Logger LOG = LoggerFactory.getLogger(Jadx.class); @@ -79,7 +79,7 @@ public class Jadx { public static List getPreDecompilePassesList() { List passes = new ArrayList<>(); passes.add(new RenameVisitor()); - passes.add(new DependencyCollector()); + passes.add(new UsageInfoVisitor()); return passes; } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 5e0bad411..9ff97eb29 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -9,6 +9,7 @@ import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; +import static jadx.core.dex.nodes.ProcessState.GENERATED; import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE; @@ -53,11 +54,16 @@ public final class ProcessClass { if (topParentClass != cls) { return generateCode(topParentClass); } + if (cls.getState() == GENERATED) { + // allow to run code generation again + cls.setState(NOT_LOADED); + } try { cls.getDependencies().forEach(ProcessClass::process); process(cls); ICodeInfo code = CodeGen.generate(cls); + cls.setState(GENERATED); cls.unload(); return code; } catch (Throwable e) { 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 f8f6f5b59..86542eb1d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -18,6 +18,7 @@ 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.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; @@ -114,6 +115,9 @@ public class ClassGen { if (cls.contains(AFlag.DONT_GENERATE)) { return; } + if (Consts.DEBUG_USAGE) { + addClassUsageInfo(code, cls); + } CodeGenUtils.addComments(code, cls); insertDecompilationProblems(code, cls); addClassDeclaration(code); @@ -378,6 +382,9 @@ public class ClassGen { if (f.contains(AFlag.DONT_GENERATE)) { return; } + if (Consts.DEBUG_USAGE) { + addFieldUsageInfo(code, f); + } CodeGenUtils.addComments(code, f); annotationGen.addForField(code, f); @@ -574,11 +581,6 @@ public class ClassGen { if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) { return shortName; } - // don't add import if class not public (must be accessed using inheritance) - ClassNode classNode = cls.root().resolveClass(extClsInfo); - if (classNode != null && !classNode.getAccessFlags().isPublic()) { - return shortName; - } if (searchCollision(cls.root(), useCls, extClsInfo)) { return fullName; } @@ -685,6 +687,40 @@ public class ClassGen { } } + private static void addClassUsageInfo(CodeWriter code, ClassNode cls) { + List deps = cls.getDependencies(); + code.startLine("// deps - ").add(Integer.toString(deps.size())); + for (ClassNode depCls : deps) { + code.startLine("// ").add(depCls.getFullName()); + } + List useIn = cls.getUseIn(); + code.startLine("// use in - ").add(Integer.toString(useIn.size())); + for (ClassNode useCls : useIn) { + code.startLine("// ").add(useCls.getFullName()); + } + List useInMths = cls.getUseInMth(); + code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); + for (MethodNode useMth : useInMths) { + code.startLine("// ").add(useMth.toString()); + } + } + + static void addMthUsageInfo(CodeWriter code, MethodNode mth) { + List useInMths = mth.getUseIn(); + code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); + for (MethodNode useMth : useInMths) { + code.startLine("// ").add(useMth.toString()); + } + } + + private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) { + List useInMths = fieldNode.getUseIn(); + code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); + for (MethodNode useMth : useInMths) { + code.startLine("// ").add(useMth.toString()); + } + } + public ClassGen getParentGen() { return parentGen == null ? this : parentGen; } 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 fd7456754..415f402fa 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -80,6 +80,9 @@ public class MethodGen { code.attachDefinition(mth); return false; } + if (Consts.DEBUG_USAGE) { + ClassGen.addMthUsageInfo(code, mth); + } addOverrideAnnotation(code, mth); annotationGen.addForMethod(code, mth); 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 0a7ad5321..8525106f1 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 @@ -42,7 +42,6 @@ import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.nodes.ProcessState.LOADED; -import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); @@ -69,8 +68,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN private ClassNode parentClass; private volatile ProcessState state = ProcessState.NOT_LOADED; + + /** Top level classes used in this class (only for top level classes, empty for inners) */ private List dependencies = Collections.emptyList(); - private List usedIn = Collections.emptyList(); + /** Classes which uses this class */ + private List useIn = Collections.emptyList(); + /** Methods which uses this class (by instructions only, definition is excluded) */ + private List useInMth = Collections.emptyList(); // cache maps private Map mthInfoMap = Collections.emptyMap(); @@ -305,7 +309,6 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN innerClasses.forEach(ClassNode::unload); fields.forEach(FieldNode::unloadAttributes); unloadAttributes(); - setState(NOT_LOADED); } private void buildCache() { @@ -585,12 +588,20 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.dependencies = dependencies; } - public List getUsedIn() { - return usedIn; + public List getUseIn() { + return useIn; } - public void setUsedIn(List usedIn) { - this.usedIn = usedIn; + public void setUseIn(List useIn) { + this.useIn = useIn; + } + + public List getUseInMth() { + return useInMth; + } + + public void setUseInMth(List useInMth) { + this.useInMth = useInMth; } @Override 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 305a38e54..7a0a60513 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 @@ -1,6 +1,8 @@ package jadx.core.dex.nodes; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import jadx.api.plugins.input.data.IFieldData; import jadx.core.dex.attributes.annotations.AnnotationsList; @@ -18,6 +20,8 @@ public class FieldNode extends LineAttrNode implements ICodeNode { private ArgType type; + private List useIn = Collections.emptyList(); + public static FieldNode build(ClassNode cls, IFieldData fieldData) { FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData); FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags()); @@ -70,6 +74,14 @@ public class FieldNode extends LineAttrNode implements ICodeNode { return parentClass; } + public List getUseIn() { + return useIn; + } + + public void setUseIn(List useIn) { + this.useIn = useIn; + } + @Override public String typeName() { return "field"; 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 7b3e2bffb..c52ebeb6a 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 @@ -40,7 +40,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.utils.Utils.lockList; -public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode { +public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable { private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class); private final MethodInfo mthInfo; @@ -72,6 +72,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, private List loops; private Region region; + private List useIn = Collections.emptyList(); + public static MethodNode build(ClassNode classNode, IMethodData methodData) { MethodNode methodNode = new MethodNode(classNode, methodData); AnnotationsList.attach(methodNode, methodData.getAnnotations()); @@ -620,6 +622,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return codeReader; } + public List getUseIn() { + return useIn; + } + + public void setUseIn(List useIn) { + this.useIn = useIn; + } + @Override public int hashCode() { return mthInfo.hashCode(); @@ -637,6 +647,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return mthInfo.equals(other.mthInfo); } + @Override + public int compareTo(@NotNull MethodNode o) { + return mthInfo.compareTo(o.mthInfo); + } + @Override public String toString() { return parentClass + "." + mthInfo.getName() diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java index 4cce135b8..847f7cdaf 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ProcessState.java @@ -4,13 +4,10 @@ public enum ProcessState { NOT_LOADED, LOADED, PROCESS_STARTED, - PROCESS_COMPLETE; - - public boolean isLoaded() { - return this != NOT_LOADED; - } + PROCESS_COMPLETE, + GENERATED; public boolean isProcessed() { - return this == PROCESS_COMPLETE; + return this == PROCESS_COMPLETE || this == GENERATED; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java deleted file mode 100644 index 9c39c31f5..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ /dev/null @@ -1,186 +0,0 @@ -package jadx.core.dex.visitors; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import jadx.api.plugins.input.data.ICodeReader; -import jadx.api.plugins.input.data.IFieldData; -import jadx.api.plugins.input.data.IMethodData; -import jadx.api.plugins.input.insns.InsnData; -import jadx.api.plugins.input.insns.Opcode; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.FieldNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.nodes.RootNode; - -@JadxVisitor( - name = "DependencyCollector", - desc = "Scan class and methods and collect dependant classes", - runAfter = { - RenameVisitor.class // sort by alias name - } -) -// TODO: store usage info for fields, methods and inner classes -public class DependencyCollector extends AbstractVisitor { - - @Override - public void init(RootNode root) { - List clsList = root.getClassesWithoutInner(); - for (ClassNode cls : clsList) { - collectClassDeps(cls); - } - buildUsageList(clsList); - } - - private void buildUsageList(List clsList) { - clsList.forEach(cls -> cls.setUsedIn(new ArrayList<>())); - for (ClassNode cls : clsList) { - for (ClassNode depCls : cls.getDependencies()) { - depCls.getUsedIn().add(cls); - } - } - for (ClassNode cls : clsList) { - List usedIn = cls.getUsedIn(); - if (usedIn.isEmpty()) { - cls.setUsedIn(Collections.emptyList()); - } else { - Collections.sort(usedIn); - } - } - } - - public void collectClassDeps(ClassNode cls) { - RootNode root = cls.root(); - Set depSet = new HashSet<>(); - processClass(cls, root, depSet); - for (ClassNode inner : cls.getInnerClasses()) { - processClass(inner, root, depSet); - } - depSet.remove(cls); - - if (depSet.isEmpty()) { - cls.setDependencies(Collections.emptyList()); - } else { - List depList = new ArrayList<>(depSet); - Collections.sort(depList); - cls.setDependencies(depList); - } - } - - private static void processClass(ClassNode cls, RootNode root, Set depList) { - addDep(root, depList, cls.getSuperClass()); - for (ArgType iType : cls.getInterfaces()) { - addDep(root, depList, iType); - } - for (FieldNode fieldNode : cls.getFields()) { - addDep(root, depList, fieldNode.getType()); - } - // TODO: process annotations and generics - for (MethodNode methodNode : cls.getMethods()) { - if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) { - continue; - } - processMethod(root, depList, methodNode); - } - } - - private static void processMethod(RootNode root, Set depList, MethodNode methodNode) { - addDep(root, depList, methodNode.getParentClass()); - addDep(root, depList, methodNode.getReturnType()); - for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) { - addDep(root, depList, arg); - } - try { - processInstructions(methodNode, depList); - } catch (Exception e) { - methodNode.getCodeReader().visitInstructions(insnData -> { - insnData.decode(); - System.out.println(insnData); - }); - methodNode.addError("Dependency scan failed", e); - } - } - - private static void processInstructions(MethodNode mth, Set deps) { - ICodeReader codeReader = mth.getCodeReader(); - if (codeReader == null) { - return; - } - RootNode root = mth.root(); - codeReader.visitInstructions(insnData -> { - try { - processInsn(root, insnData, deps); - } catch (Exception e) { - mth.addError("Dependency scan failed at insn: " + insnData, e); - } - }); - } - - private static void processInsn(RootNode root, InsnData insnData, Set deps) { - if (insnData.getOpcode() == Opcode.UNKNOWN) { - return; - } - switch (insnData.getIndexType()) { - case TYPE_REF: - insnData.decode(); - resolveType(root, deps, insnData.getIndexAsType()); - break; - case FIELD_REF: - insnData.decode(); - resolveField(root, deps, insnData.getIndexAsField()); - break; - case METHOD_REF: - insnData.decode(); - resolveMethod(root, deps, insnData.getIndexAsMethod()); - break; - } - } - - private static void resolveType(RootNode root, Set deps, String type) { - addDep(root, deps, ArgType.parse(type)); - } - - private static void resolveMethod(RootNode root, Set deps, IMethodData method) { - resolveType(root, deps, method.getParentClassType()); - } - - private static void resolveField(RootNode root, Set deps, IFieldData field) { - resolveType(root, deps, field.getParentClassType()); - } - - private static void addDep(RootNode root, Set depList, ArgType type) { - if (type != null) { - if (type.isObject() && !type.isGenericType()) { - addDep(root, depList, ClassInfo.fromType(root, type)); - ArgType[] genericTypes = type.getGenericTypes(); - if (type.isGeneric() && genericTypes != null) { - for (ArgType argType : genericTypes) { - addDep(root, depList, argType); - } - } - } else if (type.isArray()) { - addDep(root, depList, type.getArrayRootElement()); - } - } - } - - private static void addDep(RootNode root, Set depList, ClassInfo clsInfo) { - if (clsInfo != null) { - ClassNode node = root.resolveClass(clsInfo); - addDep(root, depList, node); - } - } - - private static void addDep(RootNode root, Set depList, ClassNode clsNode) { - if (clsNode != null) { - // add only top classes - depList.add(clsNode.getTopParentClass()); - } - } -} 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 a7166be8a..04d1c87a0 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 @@ -56,7 +56,7 @@ public class FixAccessModifiers extends AbstractVisitor { } private int fixClassVisibility(ClassNode cls) { - if (cls.getUsedIn().isEmpty()) { + if (cls.getUseIn().isEmpty()) { return -1; } AccessInfo accessFlags = cls.getAccessFlags(); @@ -66,7 +66,7 @@ public class FixAccessModifiers extends AbstractVisitor { } // check if private inner class is used outside ClassNode topParentClass = cls.getTopParentClass(); - for (ClassNode useCls : cls.getUsedIn()) { + for (ClassNode useCls : cls.getUseIn()) { if (useCls.getTopParentClass() != topParentClass) { return AccessFlags.PUBLIC; } @@ -74,12 +74,21 @@ public class FixAccessModifiers extends AbstractVisitor { } if (accessFlags.isPackagePrivate()) { String pkg = cls.getPackage(); - for (ClassNode useCls : cls.getUsedIn()) { + for (ClassNode useCls : cls.getUseIn()) { if (!useCls.getPackage().equals(pkg)) { return AccessFlags.PUBLIC; } } } + if (!accessFlags.isPublic()) { + // if class is used in inlinable method => make it public + for (MethodNode useMth : cls.getUseInMth()) { + boolean canInline = MethodInlineVisitor.canInline(useMth) || useMth.contains(AType.METHOD_INLINE); + if (canInline && !useMth.getUseIn().isEmpty()) { + return AccessFlags.PUBLIC; + } + } + } return -1; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index aecd0b4a8..a5f49af96 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -34,12 +34,7 @@ public class MethodInlineVisitor extends AbstractVisitor { @Override public void visit(MethodNode mth) throws JadxException { - if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) { - return; - } - AccessInfo accessFlags = mth.getAccessFlags(); - if (accessFlags.isSynthetic() && accessFlags.isStatic() - && mth.getBasicBlocks().size() == 2) { + if (canInline(mth) && mth.getBasicBlocks().size() == 2) { BlockNode returnBlock = mth.getBasicBlocks().get(1); if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) { BlockNode firstBlock = mth.getBasicBlocks().get(0); @@ -48,6 +43,14 @@ public class MethodInlineVisitor extends AbstractVisitor { } } + public static boolean canInline(MethodNode mth) { + if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) { + return false; + } + AccessInfo accessFlags = mth.getAccessFlags(); + return accessFlags.isSynthetic() && accessFlags.isStatic(); + } + private static void inlineMth(MethodNode mth, BlockNode firstBlock, BlockNode returnBlock) { List insnList = firstBlock.getInstructions(); if (insnList.isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java new file mode 100644 index 000000000..edc071199 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java @@ -0,0 +1,101 @@ +package jadx.core.dex.visitors.usage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; + +public class UsageInfo { + private final RootNode root; + + private final UseSet clsDeps = new UseSet<>(); + private final UseSet clsUsage = new UseSet<>(); + private final UseSet clsUseInMth = new UseSet<>(); + private final UseSet fieldUsage = new UseSet<>(); + private final UseSet mthUsage = new UseSet<>(); + + public UsageInfo(RootNode root) { + this.root = root; + } + + public void apply() { + clsDeps.visit((cls, deps) -> cls.setDependencies(sortedList(deps))); + clsUsage.visit((cls, deps) -> cls.setUseIn(sortedList(deps))); + clsUseInMth.visit((cls, methods) -> cls.setUseInMth(sortedList(methods))); + fieldUsage.visit((field, methods) -> field.setUseIn(sortedList(methods))); + mthUsage.visit((mth, methods) -> mth.setUseIn(sortedList(methods))); + } + + public void clsUse(ClassNode cls, ArgType useType) { + processType(useType, depCls -> clsUse(cls, depCls)); + } + + public void clsUse(MethodNode mth, ArgType useType) { + processType(useType, depCls -> clsUse(mth, depCls)); + } + + public void clsUse(MethodNode mth, ClassNode useCls) { + ClassNode parentClass = mth.getParentClass(); + clsUse(parentClass, useCls); + if (parentClass != useCls) { + // exclude class usage in self methods + clsUseInMth.add(useCls, mth); + } + } + + public void clsUse(ClassNode cls, ClassNode depCls) { + ClassNode topParentClass = cls.getTopParentClass(); + clsDeps.add(topParentClass, depCls.getTopParentClass()); + + clsUsage.add(depCls, cls); + clsUsage.add(depCls, topParentClass); + } + + /** + * Add method usage: {@code useMth} occurrence found in {@code mth} code + */ + public void methodUse(MethodNode mth, MethodNode useMth) { + clsUse(mth, useMth.getParentClass()); + mthUsage.add(useMth, mth); + } + + public void fieldUse(MethodNode mth, FieldNode useFld) { + clsUse(mth, useFld.getParentClass()); + fieldUsage.add(useFld, mth); + } + + private void processType(ArgType type, Consumer consumer) { + if (type == null) { + return; + } + if (type.isArray()) { + processType(type.getArrayRootElement(), consumer); + return; + } + if (type.isObject() && !type.isGenericType()) { + ClassNode clsNode = root.resolveClass(type); + if (clsNode != null) { + consumer.accept(clsNode); + } + ArgType[] genericTypes = type.getGenericTypes(); + if (type.isGeneric() && genericTypes != null) { + for (ArgType argType : genericTypes) { + processType(argType, consumer); + } + } + } + } + + private static > List sortedList(Set deps) { + List list = new ArrayList<>(deps); + Collections.sort(list); + return list; + } +} 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 new file mode 100644 index 000000000..c191100e6 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -0,0 +1,115 @@ +package jadx.core.dex.visitors.usage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.input.data.ICodeReader; +import jadx.api.plugins.input.insns.InsnData; +import jadx.api.plugins.input.insns.Opcode; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.info.MethodInfo; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.dex.visitors.RenameVisitor; + +@JadxVisitor( + name = "UsageInfoVisitor", + desc = "Scan class and methods to collect usage info and class dependencies", + runAfter = { + RenameVisitor.class // sort by alias name + } +) +public class UsageInfoVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class); + + @Override + public void init(RootNode root) { + long startTime = System.currentTimeMillis(); + UsageInfo usageInfo = new UsageInfo(root); + for (ClassNode cls : root.getClasses()) { + processClass(cls, usageInfo); + } + usageInfo.apply(); + LOG.debug("Dependency collection done in {}ms", System.currentTimeMillis() - startTime); + } + + private static void processClass(ClassNode cls, UsageInfo usageInfo) { + usageInfo.clsUse(cls, cls.getSuperClass()); + for (ArgType interfaceType : cls.getInterfaces()) { + usageInfo.clsUse(cls, interfaceType); + } + for (FieldNode fieldNode : cls.getFields()) { + usageInfo.clsUse(cls, fieldNode.getType()); + } + // TODO: process annotations and generics + for (MethodNode methodNode : cls.getMethods()) { + if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) { + continue; + } + processMethod(methodNode, usageInfo); + } + } + + private static void processMethod(MethodNode mth, UsageInfo usageInfo) { + ClassNode cls = mth.getParentClass(); + usageInfo.clsUse(cls, mth.getReturnType()); + for (ArgType argType : mth.getMethodInfo().getArgumentsTypes()) { + usageInfo.clsUse(cls, argType); + } + try { + processInstructions(mth, usageInfo); + } catch (Exception e) { + mth.addError("Dependency scan failed", e); + } + } + + private static void processInstructions(MethodNode mth, UsageInfo usageInfo) { + ICodeReader codeReader = mth.getCodeReader(); + if (codeReader == null) { + return; + } + RootNode root = mth.root(); + codeReader.visitInstructions(insnData -> { + try { + processInsn(root, mth, insnData, usageInfo); + } catch (Exception e) { + mth.addError("Dependency scan failed at insn: " + insnData, e); + } + }); + } + + private static void processInsn(RootNode root, MethodNode mth, InsnData insnData, UsageInfo usageInfo) { + if (insnData.getOpcode() == Opcode.UNKNOWN) { + return; + } + switch (insnData.getIndexType()) { + case TYPE_REF: + insnData.decode(); + ArgType usedType = ArgType.parse(insnData.getIndexAsType()); + usageInfo.clsUse(mth, usedType); + break; + + case FIELD_REF: + insnData.decode(); + FieldNode fieldNode = root.resolveField(FieldInfo.fromData(root, insnData.getIndexAsField())); + if (fieldNode != null) { + usageInfo.fieldUse(mth, fieldNode); + } + break; + + case METHOD_REF: + insnData.decode(); + MethodNode methodNode = root.resolveMethod(MethodInfo.fromData(root, insnData.getIndexAsMethod())); + if (methodNode != null) { + usageInfo.methodUse(mth, methodNode); + } + break; + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UseSet.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UseSet.java new file mode 100644 index 000000000..8a6cee12e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UseSet.java @@ -0,0 +1,30 @@ +package jadx.core.dex.visitors.usage; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +public class UseSet { + private final Map> useMap = new HashMap<>(); + + public void add(K obj, V use) { + if (obj == use) { + // self excluded + return; + } + Set set = useMap.computeIfAbsent(obj, k -> new HashSet<>()); + set.add(use); + } + + public Set get(K obj) { + return useMap.get(obj); + } + + public void visit(BiConsumer> consumer) { + for (Map.Entry> entry : useMap.entrySet()) { + consumer.accept(entry.getKey(), entry.getValue()); + } + } +} 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 e41160696..55b21b897 100644 --- a/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/SmaliTest.java @@ -107,6 +107,7 @@ public abstract class SmaliTest extends IntegrationTest { try { SmaliOptions options = new SmaliOptions(); options.outputDexFile = output.getAbsolutePath(); + options.verboseErrors = true; List inputFileNames = inputFiles.stream().map(File::getAbsolutePath).collect(Collectors.toList()); Smali.assemble(options, inputFileNames); } catch (Exception e) { diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java index a5a20421e..41913303d 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxAssertions.java @@ -8,10 +8,12 @@ import jadx.core.dex.nodes.ClassNode; public class JadxAssertions extends Assertions { public static JadxClassNodeAssertions assertThat(ClassNode actual) { + Assertions.assertThat(actual).isNotNull(); return new JadxClassNodeAssertions(actual); } public static JadxCodeAssertions assertThat(ICodeInfo actual) { + Assertions.assertThat(actual).isNotNull(); return new JadxCodeAssertions(actual.getCodeStr()); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestMethodInline.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestMethodInline.java new file mode 100644 index 000000000..d8c30035d --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestMethodInline.java @@ -0,0 +1,55 @@ +package jadx.tests.integration.inline; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestMethodInline extends SmaliTest { + // @formatter:off + /* + package inline; + + public class A { + public static void useMth() { + inline.other.B.bridgeMth(); // after inline 'inline.other.C.test()' is not accessible + } + } + ----------------------------------------------------------- + package inline.other; + + public class B { + public static bridge synthetic void bridgeMth() { + inline.other.C.test(); + } + } + ---------------------------------------------------------- + package inline.other; + + class C { + public static void test() { + } + } + */ + // @formatter:on + + @Test + public void test() { + List classes = loadFromSmaliFiles(); + ClassNode aCls = searchCls(classes, "inline.A"); + ClassNode bCls = searchCls(classes, "inline.other.B"); + ClassNode cCls = searchCls(classes, "inline.other.C"); + + assertThat(bCls).code().doesNotContain("bridgeMth()"); + assertThat(aCls).code().containsOne("C.test()"); + assertThat(cCls).code().containsOne("public class C {"); + + // TODO: update dependencies? + // assertThat(aCls.getDependencies()).contains(cCls); + // assertThat(cCls.getUsedIn()).contains(aCls); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFixClassAccessModifiers.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFixClassAccessModifiers.java new file mode 100644 index 000000000..437554a13 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFixClassAccessModifiers.java @@ -0,0 +1,31 @@ +package jadx.tests.integration.others; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestFixClassAccessModifiers extends SmaliTest { + // @formatter:off + /* + public Cls.InnerCls field; + + public static class Cls { + private static class InnerCls { + } + } + */ + // @formatter:on + + @Test + public void test() { + List classes = loadFromSmaliFiles(); + assertThat(searchCls(classes, "others.Cls")) + .code() + .containsOne("public static class InnerCls {"); + } +} diff --git a/jadx-core/src/test/smali/inline/TestMethodInline/A.smali b/jadx-core/src/test/smali/inline/TestMethodInline/A.smali new file mode 100644 index 000000000..fa4b8c4ac --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestMethodInline/A.smali @@ -0,0 +1,10 @@ +.class public Linline/A; +.super Ljava/lang/Object; + +.method public static useMth()V + .locals 1 + + invoke-static {}, Linline/other/B;->bridgeMth()V + + return-void +.end method diff --git a/jadx-core/src/test/smali/inline/TestMethodInline/B.smali b/jadx-core/src/test/smali/inline/TestMethodInline/B.smali new file mode 100644 index 000000000..5162d9408 --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestMethodInline/B.smali @@ -0,0 +1,10 @@ +.class public Linline/other/B; +.super Ljava/lang/Object; + +.method public static bridge synthetic bridgeMth()V + .locals 1 + + invoke-static {}, Linline/other/C;->test()V + + return-void +.end method diff --git a/jadx-core/src/test/smali/inline/TestMethodInline/C.smali b/jadx-core/src/test/smali/inline/TestMethodInline/C.smali new file mode 100644 index 000000000..3d6a6995c --- /dev/null +++ b/jadx-core/src/test/smali/inline/TestMethodInline/C.smali @@ -0,0 +1,8 @@ +.class Linline/other/C; # package-private +.super Ljava/lang/Object; + +.method public static test()V + .locals 1 + + return-void +.end method diff --git a/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/Cls.smali b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/Cls.smali new file mode 100644 index 000000000..5b6da374e --- /dev/null +++ b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/Cls.smali @@ -0,0 +1,8 @@ +.class public Lothers/Cls; +.super Ljava/lang/Object; + +.annotation system Ldalvik/annotation/MemberClasses; + value = { + Lothers/Cls$InnerCls; + } +.end annotation diff --git a/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/InnerCls.smali b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/InnerCls.smali new file mode 100644 index 000000000..437cd50b0 --- /dev/null +++ b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/InnerCls.smali @@ -0,0 +1,11 @@ +.class private Lothers/Cls$InnerCls; +.super Ljava/lang/Object; + +.annotation system Ldalvik/annotation/EnclosingClass; + value = Lothers/Cls; +.end annotation + +.annotation system Ldalvik/annotation/InnerClass; + accessFlags = 0xA + name = "InnerCls" +.end annotation diff --git a/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/TestCls.smali b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/TestCls.smali new file mode 100644 index 000000000..abf3cc950 --- /dev/null +++ b/jadx-core/src/test/smali/others/TestFixClassAccessModifiers/TestCls.smali @@ -0,0 +1,4 @@ +.class public Lothers/TestCls; +.super Ljava/lang/Object; + +.field public field:Lothers/Cls$InnerCls; From 7d29c5d7663c64c4ca45ef9b94fdf01adda31b09 Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 27 May 2020 20:53:04 +0100 Subject: [PATCH 44/55] fix: correct skip size for sparse switch payload --- .../main/java/jadx/core/dex/instructions/args/ArgType.java | 4 ++++ .../main/java/jadx/plugins/input/dex/insns/DexInsnFormat.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 9237ea5cb..ca24197e7 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 @@ -13,6 +13,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; public abstract class ArgType { public static final ArgType INT = primitive(PrimitiveType.INT); @@ -645,6 +646,9 @@ public abstract class ArgType { } public static ArgType parse(String type) { + if (type == null || type.isEmpty()) { + throw new JadxRuntimeException("Failed to parse type string: " + type); + } char f = type.charAt(0); switch (f) { case 'L': 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 07618abf4..26e70158f 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 @@ -293,7 +293,7 @@ public abstract class DexInsnFormat { @Override public void skip(DexInsnData insn, SectionReader in) { int size = in.readUShort(); - in.skip(4 + size * 4 * 2); + in.skip(size * 8); insn.setLength(size * 4 + 2); } }; From e7b00cc76ec1f72a199911aa6f0cd41ee246b811 Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 27 May 2020 20:55:32 +0100 Subject: [PATCH 45/55] fix: add methods and fields types into usage info (#729) --- .../main/java/jadx/core/dex/visitors/usage/UsageInfo.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java index edc071199..aa2c0f858 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java @@ -64,11 +64,16 @@ public class UsageInfo { public void methodUse(MethodNode mth, MethodNode useMth) { clsUse(mth, useMth.getParentClass()); mthUsage.add(useMth, mth); + // implicit usage + clsUse(mth, useMth.getReturnType()); + useMth.getMethodInfo().getArgumentsTypes().forEach(argType -> clsUse(mth, argType)); } public void fieldUse(MethodNode mth, FieldNode useFld) { clsUse(mth, useFld.getParentClass()); fieldUsage.add(useFld, mth); + // implicit usage + clsUse(mth, useFld.getType()); } private void processType(ArgType type, Consumer consumer) { From ae31fee8dd8d58e8fa007d0c201f11649966af5d Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 29 May 2020 17:39:15 +0100 Subject: [PATCH 46/55] fix: add cast to exact type on field access (#729) --- .../core/dex/instructions/IndexInsnNode.java | 13 +++++--- .../java/jadx/core/dex/nodes/RootNode.java | 5 +++ .../jadx/core/dex/visitors/ModVisitor.java | 32 +++++++++++++++++++ .../integration/types/TestFieldAccess.java | 31 ++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/types/TestFieldAccess.java diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java index 66cea5297..698f8e496 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java @@ -41,10 +41,15 @@ public class IndexInsnNode extends InsnNode { switch (insnType) { case CAST: case CHECK_CAST: - return InsnUtils.formatOffset(offset) + ": " - + InsnUtils.insnTypeToString(insnType) - + getResult() + " = (" + InsnUtils.indexToString(index) + ") " - + Utils.listToString(getArguments()); + StringBuilder sb = new StringBuilder(); + sb.append(InsnUtils.formatOffset(offset)).append(": "); + sb.append(insnType).append(' '); + if (getResult() != null) { + sb.append(getResult()).append(" = "); + } + sb.append('(').append(InsnUtils.indexToString(index)).append(") "); + sb.append(Utils.listToString(getArguments())); + return sb.toString(); default: return super.toString() + ' ' + InsnUtils.indexToString(index); 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 ee95b0297..84a63ef00 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 @@ -30,6 +30,7 @@ import jadx.core.dex.nodes.utils.MethodUtils; import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.dex.visitors.typeinference.TypeCompare; import jadx.core.dex.visitors.typeinference.TypeUpdate; import jadx.core.utils.CacheStorage; import jadx.core.utils.ErrorsCounter; @@ -424,6 +425,10 @@ public class RootNode { return typeUpdate; } + public TypeCompare getTypeCompare() { + return typeUpdate.getTypeCompare(); + } + public ICodeCache getCodeCache() { return codeCache; } 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 93388a8ed..b062702df 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 @@ -33,6 +33,7 @@ import jadx.core.dex.instructions.NewArrayNode; import jadx.core.dex.instructions.SwitchInsn; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.NamedArg; import jadx.core.dex.instructions.args.RegisterArg; @@ -49,6 +50,7 @@ import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; +import jadx.core.dex.visitors.typeinference.TypeCompareEnum; import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxException; @@ -144,6 +146,11 @@ public class ModVisitor extends AbstractVisitor { fixPrimitiveCast(mth, block, i, insn); break; + case IPUT: + case IGET: + fixTypeForFieldAccess(mth, (IndexInsnNode) insn); + break; + default: break; } @@ -152,6 +159,31 @@ public class ModVisitor extends AbstractVisitor { } } + private static void fixTypeForFieldAccess(MethodNode mth, IndexInsnNode insn) { + InsnArg instanceArg = insn.getArg(insn.getType() == InsnType.IGET ? 0 : 1); + if (instanceArg.contains(AFlag.SUPER)) { + return; + } + if (instanceArg.isInsnWrap() && ((InsnWrapArg) instanceArg).getWrapInsn().getType() == InsnType.CAST) { + return; + } + FieldInfo fieldInfo = (FieldInfo) insn.getIndex(); + ArgType clsType = fieldInfo.getDeclClass().getType(); + ArgType instanceType = instanceArg.getType(); + TypeCompareEnum result = mth.root().getTypeCompare().compareTypes(instanceType, clsType); + if (result.isEqual() || (result == TypeCompareEnum.NARROW_BY_GENERIC && !instanceType.isGenericType())) { + return; + } + IndexInsnNode castInsn = new IndexInsnNode(InsnType.CAST, clsType, 1); + castInsn.addArg(instanceArg.duplicate()); + castInsn.add(AFlag.EXPLICIT_CAST); + + InsnArg castArg = InsnArg.wrapInsnIntoArg(castInsn); + castArg.setType(clsType); + insn.replaceArg(instanceArg, castArg); + InsnRemover.unbindArgUsage(mth, instanceArg); + } + private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) { int[] keys = insn.getKeys(); int len = keys.length; diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestFieldAccess.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestFieldAccess.java new file mode 100644 index 000000000..d304af20b --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestFieldAccess.java @@ -0,0 +1,31 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestFieldAccess extends IntegrationTest { + + public static class TestCls { + private String field; + + static T testPut(T t) { + ((TestCls) t).field = ""; + return t; + } + + static T testGet(T t) { + System.out.println(((TestCls) t).field); + return t; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("t.field"); + } +} From 6192ced214dc05f6848ca786d2d38a900f717bbe Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 1 Jun 2020 19:59:28 +0100 Subject: [PATCH 47/55] fix: improve type inference of type variables in method invoke (#913) --- jadx-core/src/main/java/jadx/core/Consts.java | 1 + .../java/jadx/core/dex/attributes/AType.java | 2 + .../attributes/nodes/MethodTypeVarsAttr.java | 33 +++++ .../core/dex/instructions/args/ArgType.java | 31 ++++ .../java/jadx/core/dex/nodes/ClassNode.java | 11 ++ .../jadx/core/dex/nodes/utils/TypeUtils.java | 39 ++++- .../debuginfo/DebugInfoApplyVisitor.java | 2 +- .../typeinference/TypeInferenceVisitor.java | 77 +++++----- .../visitors/typeinference/TypeSearch.java | 6 +- .../visitors/typeinference/TypeUpdate.java | 135 +++++++++++------- .../typeinference/TypeUpdateInfo.java | 9 +- .../invoke/TestCastInOverloadedInvoke.java | 5 +- .../invoke/TestHierarchyOverloadedInvoke.java | 12 +- .../integration/types/TestGenerics6.java | 70 +++++++++ 14 files changed, 325 insertions(+), 108 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics6.java diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index f76c23b93..908047fe0 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -3,6 +3,7 @@ package jadx.core; public class Consts { public static final boolean DEBUG = false; public static final boolean DEBUG_USAGE = false; + public static final boolean DEBUG_TYPE_INFERENCE = false; public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_STRING = "java.lang.String"; 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 e9be49206..d94edf0c3 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 @@ -21,6 +21,7 @@ import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr; +import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr; @@ -64,6 +65,7 @@ public class 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<>(); // region public static final AType DECLARE_VARIABLES = new AType<>(); 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 new file mode 100644 index 000000000..646f5ddea --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodTypeVarsAttr.java @@ -0,0 +1,33 @@ +package jadx.core.dex.attributes.nodes; + +import java.util.Set; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.instructions.args.ArgType; + +/** + * Set of known type variables at current method + */ +public class MethodTypeVarsAttr implements IAttribute { + + private final Set typeVars; + + public MethodTypeVarsAttr(Set typeVars) { + this.typeVars = typeVars; + } + + public Set getTypeVars() { + return typeVars; + } + + @Override + public AType getType() { + return AType.METHOD_TYPE_VARS; + } + + @Override + public String toString() { + return "TYPE_VARS: " + typeVars; + } +} 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 ca24197e7..688a075e4 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 @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.function.Function; import org.jetbrains.annotations.NotNull; @@ -744,6 +745,36 @@ public abstract class ArgType { return false; } + /** + * Recursively visit all subtypes of this type. + * To exit return non-null value. + */ + public R visitTypes(Function visitor) { + R r = visitor.apply(this); + if (r != null) { + return r; + } + ArgType wildcardType = getWildcardType(); + if (wildcardType != null) { + return wildcardType.visitTypes(visitor); + } + if (isArray()) { + ArgType arrayElement = getArrayElement(); + if (arrayElement != null) { + return arrayElement.visitTypes(visitor); + } + } + if (isGeneric()) { + ArgType[] genericTypes = getGenericTypes(); + if (genericTypes != null) { + for (ArgType genericType : genericTypes) { + return genericType.visitTypes(visitor); + } + } + } + return null; + } + public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) { if (!type.isObject() || type.isGenericType()) { return type; 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 8525106f1..6c725145a 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 @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; @@ -425,6 +426,16 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return parent == this ? this : parent.getTopParentClass(); } + public void visitParentClasses(Consumer consumer) { + ClassNode currentCls = this; + ClassNode parentCls = currentCls.getParentClass(); + while (parentCls != currentCls) { + consumer.accept(parentCls); + currentCls = parentCls; + parentCls = currentCls.getParentClass(); + } + } + public boolean hasNotGeneratedParent() { if (contains(AFlag.DONT_GENERATE)) { return true; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java index 2291ee2c0..f16f1e7c0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/utils/TypeUtils.java @@ -2,19 +2,23 @@ package jadx.core.dex.nodes.utils; 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import jadx.core.clsp.ClspClass; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.GenericTypeParameter; import jadx.core.dex.nodes.IMethodDetails; +import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; public class TypeUtils { @@ -24,7 +28,6 @@ public class TypeUtils { this.root = rootNode; } - @NotNull public List getClassGenerics(ArgType type) { ClassNode classNode = root.resolveClass(type); if (classNode != null) { @@ -38,6 +41,38 @@ public class TypeUtils { return generics == null ? Collections.emptyList() : generics; } + public Set getKnownTypeVarsAtMethod(MethodNode mth) { + MethodTypeVarsAttr typeVarsAttr = mth.get(AType.METHOD_TYPE_VARS); + if (typeVarsAttr != null) { + return typeVarsAttr.getTypeVars(); + } + Set typeVars = collectKnownTypeVarsAtMethod(mth); + mth.addAttr(new MethodTypeVarsAttr(typeVars)); + return typeVars; + } + + private static Set collectKnownTypeVarsAtMethod(MethodNode mth) { + Set typeVars = new HashSet<>(); + ClassNode declCls = mth.getParentClass(); + addTypeVarsFromCls(typeVars, declCls); + declCls.visitParentClasses(parent -> addTypeVarsFromCls(typeVars, parent)); + + for (GenericTypeParameter typeParameter : mth.getTypeParameters()) { + typeVars.add(typeParameter.getTypeVariable()); + } + return typeVars.isEmpty() ? Collections.emptySet() : typeVars; + } + + private static void addTypeVarsFromCls(Set typeVars, ClassNode parentCls) { + List typeParameters = parentCls.getGenericTypeParameters(); + if (typeParameters.isEmpty()) { + return; + } + for (GenericTypeParameter typeParameter : typeParameters) { + typeVars.add(typeParameter.getTypeVariable()); + } + } + /** * Replace generic types in {@code typeWithGeneric} using instance types *
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index f893018f3..bc7734330 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -143,7 +143,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor { } public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) { - TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(ssaVar, type); + TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(mth, ssaVar, type); if (result == TypeUpdateResult.REJECT) { if (Consts.DEBUG) { LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 0d51c31da..c408f0634 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -72,7 +72,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (mth.isNoCode()) { return; } - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.info("Start type inference in method: {}", mth); } if (resolveTypes(mth)) { @@ -103,20 +103,21 @@ public final class TypeInferenceVisitor extends AbstractVisitor { /** * Guess type from usage and try to set it to current variable - * and all connected instructions with {@link TypeUpdate#apply(SSAVar, ArgType)} + * and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)} */ private boolean runTypePropagation(MethodNode mth) { + List ssaVars = mth.getSVars(); // collect initial type bounds from assign and usages` - mth.getSVars().forEach(this::attachBounds); - mth.getSVars().forEach(this::mergePhiBounds); + ssaVars.forEach(this::attachBounds); + ssaVars.forEach(this::mergePhiBounds); // start initial type propagation - mth.getSVars().forEach(this::setImmutableType); - mth.getSVars().forEach(this::setBestType); + ssaVars.forEach(var -> setImmutableType(mth, var)); + ssaVars.forEach(var -> setBestType(mth, var)); // try other types if type is still unknown boolean resolved = true; - for (SSAVar var : mth.getSVars()) { + for (SSAVar var : ssaVars) { ArgType type = var.getTypeInfo().getType(); if (!type.isTypeKnown() && !var.isTypeImmutable() @@ -131,7 +132,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { TypeSearch typeSearch = new TypeSearch(mth); try { if (!typeSearch.run()) { - mth.addWarn("Multi-variable type inference failed"); + mth.addWarnComment("Multi-variable type inference failed"); } for (SSAVar var : mth.getSVars()) { if (!var.getTypeInfo().getType().isTypeKnown()) { @@ -140,50 +141,44 @@ public final class TypeInferenceVisitor extends AbstractVisitor { } return true; } catch (Exception e) { - mth.addWarn("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); + mth.addWarnComment("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); return false; } } - private boolean setImmutableType(SSAVar ssaVar) { + private void setImmutableType(MethodNode mth, SSAVar ssaVar) { try { ArgType immutableType = ssaVar.getImmutableType(); if (immutableType != null) { - return applyImmutableType(ssaVar, immutableType); + applyImmutableType(mth, ssaVar, immutableType); } - return false; } catch (Exception e) { LOG.error("Failed to set immutable type for var: {}", ssaVar, e); - return false; } } - private boolean setBestType(SSAVar ssaVar) { + private boolean setBestType(MethodNode mth, SSAVar ssaVar) { try { - return calculateFromBounds(ssaVar); + return calculateFromBounds(mth, ssaVar); } catch (Exception e) { LOG.error("Failed to calculate best type for var: {}", ssaVar, e); return false; } } - private boolean applyImmutableType(SSAVar ssaVar, ArgType initType) { - TypeUpdateResult result = typeUpdate.apply(ssaVar, initType); - if (result == TypeUpdateResult.REJECT) { - if (Consts.DEBUG) { - LOG.info("Reject initial immutable type {} for {}", initType, ssaVar); - } - return false; + private void applyImmutableType(MethodNode mth, SSAVar ssaVar, ArgType initType) { + TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, initType); + if (Consts.DEBUG_TYPE_INFERENCE && result == TypeUpdateResult.REJECT) { + LOG.info("Reject initial immutable type {} for {}", initType, ssaVar); } - return result == TypeUpdateResult.CHANGED; } - private boolean calculateFromBounds(SSAVar ssaVar) { + private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) { TypeInfo typeInfo = ssaVar.getTypeInfo(); Set bounds = typeInfo.getBounds(); Optional bestTypeOpt = selectBestTypeFromBounds(bounds); if (!bestTypeOpt.isPresent()) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.warn("Failed to select best type from bounds, count={} : ", bounds.size()); for (ITypeBound bound : bounds) { LOG.warn(" {}", bound); @@ -192,9 +187,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return false; } ArgType candidateType = bestTypeOpt.get(); - TypeUpdateResult result = typeUpdate.apply(ssaVar, candidateType); + TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType); if (result == TypeUpdateResult.REJECT) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { if (ssaVar.getTypeInfo().getType().equals(candidateType)) { LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); } else if (candidateType.isTypeKnown()) { @@ -235,7 +230,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor { } private void addBound(TypeInfo typeInfo, ITypeBound bound) { - if (bound != null && bound.getType() != ArgType.UNKNOWN) { + if (bound == null) { + return; + } + if (bound instanceof ITypeBoundDynamic + || bound.getType() != ArgType.UNKNOWN) { typeInfo.getBounds().add(bound); } } @@ -333,10 +332,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return new TypeBoundInvokeUse(root, invoke, regArg, argType); } - private boolean tryPossibleTypes(SSAVar var, ArgType type) { + private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) { List types = makePossibleTypesList(type); for (ArgType candidateType : types) { - TypeUpdateResult result = typeUpdate.apply(var, candidateType); + TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType); if (result == TypeUpdateResult.CHANGED) { return true; } @@ -362,11 +361,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor { private boolean tryDeduceType(MethodNode mth, SSAVar var, @Nullable ArgType type) { // try best type from bounds again - if (setBestType(var)) { + if (setBestType(mth, var)) { return true; } // try all possible types (useful for primitives) - if (type != null && tryPossibleTypes(var, type)) { + if (type != null && tryPossibleTypes(mth, var, type)) { return true; } // for objects try super types @@ -412,7 +411,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) { if (objType.isObject() && objType.containsGeneric()) { ArgType rawType = ArgType.object(objType.getObject()); - TypeUpdateResult result = typeUpdate.applyWithWiderAllow(var, rawType); + TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType); return result == TypeUpdateResult.CHANGED; } return false; @@ -575,7 +574,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { for (ArgType objType : objTypes) { for (String ancestor : clsp.getSuperTypes(objType.getObject())) { ArgType ancestorType = ArgType.object(ancestor); - TypeUpdateResult result = typeUpdate.applyWithWiderAllow(var, ancestorType); + TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, ancestorType); if (result == TypeUpdateResult.CHANGED) { return true; } @@ -588,7 +587,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (var.getTypeInfo().getType() == ArgType.BOOLEAN) { for (ITypeBound bound : var.getTypeInfo().getBounds()) { if (bound.getBound() == BoundEnum.USE - && bound.getType().isPrimitive() && bound.getType() != ArgType.BOOLEAN) { + && bound.getType().isPrimitive() + && bound.getType() != ArgType.BOOLEAN + && bound.getArg() != null) { InsnNode insn = bound.getArg().getParentInsn(); if (insn == null || insn.getType() == InsnType.CAST) { continue; @@ -612,8 +613,10 @@ public final class TypeInferenceVisitor extends AbstractVisitor { } BlockNode blockNode = BlockUtils.getBlockByInsn(mth, insn); - List insnList = blockNode.getInstructions(); - insnList.add(insnList.indexOf(insn), castNode); + if (blockNode != null) { + List insnList = blockNode.getInstructions(); + insnList.add(insnList.indexOf(insn), castNode); + } } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java index 3a4e139a6..dd1b3df4c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java @@ -63,7 +63,7 @@ public class TypeSearch { } else { search(vars); searchSuccess = fullCheck(vars); - if (Consts.DEBUG && !searchSuccess) { + if (Consts.DEBUG_TYPE_INFERENCE && !searchSuccess) { LOG.warn("Multi-variable search failed in {}", mth); } } @@ -86,7 +86,7 @@ public class TypeSearch { // exclude unknown variables continue; } - TypeUpdateResult res = typeUpdate.applyWithWiderIgnSame(var.getVar(), var.getCurrentType()); + TypeUpdateResult res = typeUpdate.applyWithWiderIgnSame(mth, var.getVar(), var.getCurrentType()); if (res == TypeUpdateResult.REJECT) { mth.addComment("JADX DEBUG: Multi-variable search result rejected for " + var); applySuccess = false; @@ -97,7 +97,7 @@ public class TypeSearch { private boolean search(List vars) { int len = vars.size(); - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Run search for {} vars: ", len); StringBuilder sb = new StringBuilder(); long count = 1; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index fa18c5a3e..a690b69ba 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,6 +24,7 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.utils.TypeUtils; import jadx.core.utils.exceptions.JadxOverflowException; @@ -47,30 +50,30 @@ public final class TypeUpdate { /** * Perform recursive type checking and type propagation for all related variables */ - public TypeUpdateResult apply(SSAVar ssaVar, ArgType candidateType) { - return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY); + public TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { + return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY); } /** * Allow wider types for apply from debug info and some special cases */ - public TypeUpdateResult applyWithWiderAllow(SSAVar ssaVar, ArgType candidateType) { - return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER); + public TypeUpdateResult applyWithWiderAllow(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { + return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER); } /** * Force type setting */ - public TypeUpdateResult applyWithWiderIgnSame(SSAVar ssaVar, ArgType candidateType) { - return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME); + public TypeUpdateResult applyWithWiderIgnSame(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { + return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME); } - private TypeUpdateResult apply(SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) { + private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) { if (candidateType == null || !candidateType.isTypeKnown()) { return REJECT; } - TypeUpdateInfo updateInfo = new TypeUpdateInfo(flags); + TypeUpdateInfo updateInfo = new TypeUpdateInfo(mth, flags); TypeUpdateResult result = updateTypeChecked(updateInfo, ssaVar.getAssign(), candidateType); if (result == REJECT) { return result; @@ -79,7 +82,7 @@ public final class TypeUpdate { if (updates.isEmpty()) { return SAME; } - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Applying types for {} -> {}", ssaVar, candidateType); updates.forEach(updateEntry -> LOG.debug(" {} -> {}, insn: {}", updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn())); @@ -102,13 +105,13 @@ public final class TypeUpdate { if (compareResult == TypeCompareEnum.EQUAL) { return SAME; } - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Type rejected for {} due to conflict: candidate={}, current={}", arg, candidateType, currentType); } return REJECT; } if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType); } return REJECT; @@ -124,13 +127,13 @@ public final class TypeUpdate { TypeInfo typeInfo = ssaVar.getTypeInfo(); ArgType immutableType = ssaVar.getImmutableType(); if (immutableType != null && !Objects.equals(immutableType, candidateType)) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.info("Reject change immutable type {} to {} for {}", immutableType, candidateType, ssaVar); } return REJECT; } if (!inBounds(updateInfo, typeInfo.getBounds(), candidateType)) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Reject type '{}' for {} by bounds: {}", candidateType, ssaVar, typeInfo.getBounds()); } return REJECT; @@ -164,7 +167,7 @@ public final class TypeUpdate { } updateInfo.requestUpdate(arg, candidateType); if (updateInfo.getUpdates().size() > 500) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.error("Type update error: too deep update tree"); } return REJECT; @@ -287,55 +290,89 @@ public final class TypeUpdate { // TODO: implement backward type propagation (from result to instance) return SAME; } - if (invoke.getInstanceArg() == arg && candidateType.containsGeneric()) { - // resolve result and arg types from generic instance type + if (invoke.getInstanceArg() == arg) { IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invoke); if (methodDetails == null) { return SAME; } TypeUtils typeUtils = root.getTypeUtils(); + Set knownTypeVars = typeUtils.getKnownTypeVarsAtMethod(updateInfo.getMth()); Map typeVarsMap = typeUtils.getTypeVariablesMapping(candidateType); - if (typeVarsMap.isEmpty()) { - return SAME; - } - boolean allSame = true; - if (invoke.getResult() != null) { - ArgType returnType = typeUtils.replaceTypeVariablesUsingMap(methodDetails.getReturnType(), typeVarsMap); - if (returnType != null) { - TypeUpdateResult result = updateTypeChecked(updateInfo, invoke.getResult(), returnType); - if (result == REJECT) { - return REJECT; - } - if (result == CHANGED) { - allSame = false; - } - } - } - - int argOffset = invoke.getFirstArgOffset(); + ArgType returnType = methodDetails.getReturnType(); List argTypes = methodDetails.getArgTypes(); int argsCount = argTypes.size(); - for (int i = 0; i < argsCount; i++) { - ArgType genericArgType = argTypes.get(i); - ArgType resultArgType = typeUtils.replaceClassGenerics(candidateType, genericArgType); - if (resultArgType != null) { - InsnArg invokeArg = invoke.getArg(argOffset + i); - TypeUpdateResult result = updateTypeChecked(updateInfo, invokeArg, resultArgType); - if (result == REJECT) { - return REJECT; - } - if (result == CHANGED) { - allSame = false; - } - } + if (typeVarsMap.isEmpty()) { + // generics can't be resolved => use as is + return applyInvokeTypes(updateInfo, invoke, argsCount, knownTypeVars, () -> returnType, argTypes::get); } - return allSame ? SAME : CHANGED; + // resolve types before apply + return applyInvokeTypes(updateInfo, invoke, argsCount, knownTypeVars, + () -> typeUtils.replaceTypeVariablesUsingMap(returnType, typeVarsMap), + argNum -> typeUtils.replaceClassGenerics(candidateType, argTypes.get(argNum))); } return SAME; } + private TypeUpdateResult applyInvokeTypes(TypeUpdateInfo updateInfo, BaseInvokeNode invoke, int argsCount, + Set knownTypeVars, Supplier getReturnType, Function getArgType) { + boolean allSame = true; + RegisterArg resultArg = invoke.getResult(); + if (resultArg != null && !resultArg.isTypeImmutable()) { + ArgType returnType = checkType(knownTypeVars, getReturnType.get()); + if (returnType != null) { + TypeUpdateResult result = updateTypeChecked(updateInfo, resultArg, returnType); + if (result == REJECT) { + TypeCompareEnum compare = comparator.compareTypes(returnType, resultArg.getType()); + if (compare.isWider()) { + return REJECT; + } + } + if (result == CHANGED) { + allSame = false; + } + } + } + int argOffset = invoke.getFirstArgOffset(); + for (int i = 0; i < argsCount; i++) { + InsnArg invokeArg = invoke.getArg(argOffset + i); + if (!invokeArg.isTypeImmutable()) { + ArgType argType = checkType(knownTypeVars, getArgType.apply(i)); + if (argType != null) { + TypeUpdateResult result = updateTypeChecked(updateInfo, invokeArg, argType); + if (result == REJECT) { + TypeCompareEnum compare = comparator.compareTypes(argType, invokeArg.getType()); + if (compare.isNarrow()) { + return REJECT; + } + } + if (result == CHANGED) { + allSame = false; + } + } + } + } + return allSame ? SAME : CHANGED; + } + + @Nullable + private ArgType checkType(Set knownTypeVars, @Nullable ArgType type) { + if (type == null) { + return null; + } + if (type.containsTypeVariable()) { + if (knownTypeVars.isEmpty()) { + return null; + } + Boolean hasUnknown = type.visitTypes(t -> t.isGenericType() && !knownTypeVars.contains(t) ? Boolean.TRUE : null); + if (hasUnknown != null) { + return null; + } + } + return type; + } + private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult(); return updateTypeChecked(updateInfo, changeArg, candidateType); @@ -356,7 +393,7 @@ public final class TypeUpdate { TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType); if (result == SAME && !correctType) { - if (Consts.DEBUG) { + if (Consts.DEBUG_TYPE_INFERENCE) { LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}", candidateType, changeArg.getType(), changeArg, insn); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java index 1b2a84ced..b2e8911cc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java @@ -5,12 +5,15 @@ import java.util.List; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.nodes.MethodNode; public class TypeUpdateInfo { + private final MethodNode mth; private final TypeUpdateFlags flags; private final List updates = new ArrayList<>(); - public TypeUpdateInfo(TypeUpdateFlags flags) { + public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags) { + this.mth = mth; this.flags = flags; } @@ -50,6 +53,10 @@ public class TypeUpdateInfo { updates.removeIf(updateEntry -> updateEntry.getArg() == arg); } + public MethodNode getMth() { + return mth; + } + public List getUpdates() { return updates; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java index 1a081f927..a175d7baf 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java @@ -64,7 +64,7 @@ public class TestCastInOverloadedInvoke extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); - assertThat(code, containsOne("call((ArrayList) new ArrayList());")); + assertThat(code, containsOne("call(new ArrayList<>());")); assertThat(code, containsOne("call((List) new ArrayList());")); assertThat(code, containsOne("call((String) obj);")); @@ -76,9 +76,6 @@ public class TestCastInOverloadedInvoke extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); - assertThat(code, containsOne("call(new ArrayList<>());")); assertThat(code, containsOne("call((List) new ArrayList());")); - - assertThat(code, containsOne("call((String) obj);")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java index 7569de41a..30b1ffb2b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestHierarchyOverloadedInvoke.java @@ -5,7 +5,6 @@ import java.util.List; import org.junit.jupiter.api.Test; -import jadx.NotYetImplemented; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; @@ -83,18 +82,9 @@ public class TestHierarchyOverloadedInvoke extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); - assertThat(code, containsOne("b.call((ArrayList) new ArrayList());")); + assertThat(code, containsOne("b.call(new ArrayList<>());")); assertThat(code, containsOne("b.call((List) new ArrayList());")); assertThat(code, containsOne("b.call((String) obj);")); } - - @NotYetImplemented - @Test - public void test2() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("b.call(new ArrayList<>());")); - } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics6.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics6.java new file mode 100644 index 000000000..d4913ef54 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestGenerics6.java @@ -0,0 +1,70 @@ +package jadx.tests.integration.types; + +import java.util.Iterator; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestGenerics6 extends IntegrationTest { + + public static class TestCls implements Iterable> { + public V test(K key, V v) { + Entry entry = get(key); + if (entry != null) { + return entry.mValue; + } + put(key, v); + return null; + } + + protected Entry get(K k) { + return null; + } + + protected Entry put(K key, V v) { + return null; + } + + @Override + public Iterator> iterator() { + return null; + } + + static class Entry implements Map.Entry { + final V mValue; + + Entry(K key, V value) { + this.mValue = value; + } + + @Override + public K getKey() { + return null; + } + + @Override + public V getValue() { + return null; + } + + @Override + public V setValue(V value) { + return null; + } + } + + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .doesNotContain("Entry entry = get(k);") + .containsOne("Entry entry = get(k);"); + } +} From 5e62b9077aecb9448bea5459b308d836893d21f3 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 1 Jun 2020 21:38:52 +0100 Subject: [PATCH 48/55] fix: resolve ClassCastException on encoded value access (#946) --- jadx-core/src/main/java/jadx/core/codegen/RegionGen.java | 4 ++-- jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 bb394fc0d..dd0852d50 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -287,9 +287,9 @@ public class RegionGen extends InsnGen { staticField(code, fn.getFieldInfo()); // print original value, sometimes replaced with incorrect field FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT); - if (valueAttr != null) { + if (valueAttr != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) { Object value = valueAttr.getEncodedValue(); - if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) { + if (value != null) { code.add(" /*").add(value.toString()).add("*/"); } } 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 a605c4d44..0b299eec4 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 @@ -72,9 +72,9 @@ public class ConstStorage { if (accFlags.isStatic() && accFlags.isFinal()) { FieldInitAttr fv = f.get(AType.FIELD_INIT); if (fv != null - && fv.getEncodedValue() != null && fv.getValueType() == FieldInitAttr.InitType.CONST - && fv != FieldInitAttr.NULL_VALUE) { + && fv != FieldInitAttr.NULL_VALUE + && fv.getEncodedValue() != null) { addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic()); } } From 440357d2e832b823616dfc809d44881eabcb4ddd Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 1 Jun 2020 22:24:42 +0100 Subject: [PATCH 49/55] fix: allow cross-block move inline (#946) Signed-off-by: Skylot --- jadx-core/src/main/java/jadx/core/Jadx.java | 3 -- .../core/dex/visitors/MoveInlineVisitor.java | 38 +++++++------------ .../tests/integration/TestReturnWrapping.java | 22 ++++------- 3 files changed, 22 insertions(+), 41 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 83cbfa589..09e97771e 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -113,9 +113,6 @@ public class Jadx { passes.add(new MarkFinallyVisitor()); passes.add(new ConstInlineVisitor()); passes.add(new TypeInferenceVisitor()); - if (args.isRawCFGOutput()) { - passes.add(DotGraphVisitor.dumpRaw()); - } if (args.isDebugInfo()) { passes.add(new DebugInfoApplyVisitor()); } 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 766faddf8..d01705456 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 @@ -1,7 +1,6 @@ package jadx.core.dex.visitors; import java.util.ArrayList; -import java.util.List; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; @@ -35,12 +34,9 @@ public class MoveInlineVisitor extends AbstractVisitor { InsnRemover remover = new InsnRemover(mth); for (BlockNode block : mth.getBasicBlocks()) { remover.setBlock(block); - List insns = block.getInstructions(); - int size = insns.size(); - for (int i = 0; i < size; i++) { - InsnNode insn = insns.get(i); + for (InsnNode insn : block.getInstructions()) { if (insn.getType() == InsnType.MOVE - && processMove(mth, block, insn, i)) { + && processMove(mth, insn)) { remover.addAndUnbind(insn); } } @@ -48,7 +44,7 @@ public class MoveInlineVisitor extends AbstractVisitor { } } - private static boolean processMove(MethodNode mth, BlockNode block, InsnNode move, int i) { + private static boolean processMove(MethodNode mth, InsnNode move) { RegisterArg resultArg = move.getResult(); InsnArg moveArg = move.getArg(0); if (resultArg.sameRegAndSVar(moveArg)) { @@ -58,21 +54,26 @@ public class MoveInlineVisitor extends AbstractVisitor { if (ssaVar.isUsedInPhi()) { return false; } - RegDebugInfoAttr debugInfo = resultArg.get(AType.REG_DEBUG_INFO); + RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO); for (RegisterArg useArg : ssaVar.getUseList()) { InsnNode useInsn = useArg.getParentInsn(); - if (useInsn == null || !fromThisBlock(block, useInsn, i)) { + if (useInsn == null) { return false; } - RegDebugInfoAttr debugInfoAttr = useArg.get(AType.REG_DEBUG_INFO); - if (debugInfoAttr != null) { - debugInfo = debugInfoAttr; + if (debugInfo == null) { + RegDebugInfoAttr debugInfoAttr = useArg.get(AType.REG_DEBUG_INFO); + if (debugInfoAttr != null) { + debugInfo = debugInfoAttr; + } } } // all checks passed, execute inline for (RegisterArg useArg : new ArrayList<>(ssaVar.getUseList())) { InsnNode useInsn = useArg.getParentInsn(); + if (useInsn == null) { + continue; + } InsnArg replaceArg; if (moveArg.isRegister()) { replaceArg = ((RegisterArg) moveArg).duplicate(useArg.getInitType()); @@ -83,21 +84,10 @@ public class MoveInlineVisitor extends AbstractVisitor { if (debugInfo != null) { replaceArg.addAttr(debugInfo); } - if (useInsn == null || !useInsn.replaceArg(useArg, replaceArg)) { + if (!useInsn.replaceArg(useArg, replaceArg)) { mth.addWarnComment("Failed to replace arg in insn: " + useInsn); } } return true; } - - private static boolean fromThisBlock(BlockNode block, InsnNode insn, int curPos) { - List list = block.getInstructions(); - int size = list.size(); - for (int j = curPos; j < size; j++) { - if (list.get(j) == insn) { - return true; - } - } - return false; - } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/TestReturnWrapping.java b/jadx-core/src/test/java/jadx/tests/integration/TestReturnWrapping.java index a74aaaf52..763e3fd77 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/TestReturnWrapping.java +++ b/jadx-core/src/test/java/jadx/tests/integration/TestReturnWrapping.java @@ -24,15 +24,14 @@ public class TestReturnWrapping extends IntegrationTest { int i = arg1; if (arg0 == null) { return ret + Integer.toHexString(i); - } else { - i++; - try { - ret = new Object().getClass(); - } catch (Exception e) { - ret = "Qwerty"; - } - return i > 128 ? arg0.toString() + ret.toString() : i; } + i++; + try { + ret = new Object().getClass(); + } catch (Exception e) { + ret = "Qwerty"; + } + return i > 128 ? arg0.toString() + ret.toString() : i; } public static int f3(int arg0) { @@ -54,12 +53,7 @@ public class TestReturnWrapping extends IntegrationTest { assertThat(code, containsString("return 255;")); assertThat(code, containsString("return arg0 + 1;")); - - // TODO: reduce code vars by name - // assertThat(code, containsString("return i > 128 ? arg0.toString() + ret.toString() : - // Integer.valueOf(i);")); - assertThat(code, containsString("return i2 > 128 ? arg0.toString() + ret.toString() : Integer.valueOf(i2);")); - + assertThat(code, containsString("return i > 128 ? arg0.toString() + ret.toString() : Integer.valueOf(i);")); assertThat(code, containsString("return arg0 + 2;")); assertThat(code, containsString("arg0 -= 951;")); } From 65553c156cd0edbf5c75770e76de621984891255 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 6 Jun 2020 18:36:12 +0100 Subject: [PATCH 50/55] fix: restore android R class (#947) (regression) --- .../java/jadx/core/dex/nodes/ClassNode.java | 13 +++- .../java/jadx/core/dex/nodes/RootNode.java | 3 +- .../utils/android/AndroidResourcesUtils.java | 21 +++---- .../{inner => android}/TestRFieldAccess.java | 2 +- .../android/TestRFieldRestore.java | 63 +++++++++++++++++++ .../TestRFieldRestore2.java | 2 +- .../TestRFieldRestore3.java | 2 +- .../integration/inner/TestRFieldRestore.java | 38 ----------- 8 files changed, 85 insertions(+), 59 deletions(-) rename jadx-core/src/test/java/jadx/tests/integration/{inner => android}/TestRFieldAccess.java (94%) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java rename jadx-core/src/test/java/jadx/tests/integration/{inner => android}/TestRFieldRestore2.java (94%) rename jadx-core/src/test/java/jadx/tests/integration/{inner => android}/TestRFieldRestore3.java (97%) delete mode 100644 jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java 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 6c725145a..5f7b173d8 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 @@ -49,6 +49,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN private final RootNode root; private final int clsDefOffset; + @Nullable private final Path inputPath; private final ClassInfo clsInfo; @@ -132,8 +133,16 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); } - // empty synthetic class - public ClassNode(RootNode root, String name, int accessFlags) { + public static ClassNode addSyntheticClass(RootNode root, String name, int accessFlags) { + ClassNode cls = new ClassNode(root, name, accessFlags); + cls.add(AFlag.SYNTHETIC); + cls.setState(ProcessState.PROCESS_COMPLETE); + root.addClassNode(cls); + return cls; + } + + // Create empty class + private ClassNode(RootNode root, String name, int accessFlags) { this.root = root; this.inputPath = null; this.clsDefOffset = 0; 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 84a63ef00..93ac20714 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 @@ -106,9 +106,8 @@ public class RootNode { if (name == null || name.isEmpty()) { name = "CLASS_" + typeStr; } - ClassNode clsNode = new ClassNode(this, name, classData.getAccessFlags()); + ClassNode clsNode = ClassNode.addSyntheticClass(this, name, classData.getAccessFlags()); ErrorsCounter.error(clsNode, "Load error", exc); - addClassNode(clsNode); } public void addClassNode(ClassNode clsNode) { 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 d59accfb8..393ddbe3e 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 @@ -6,7 +6,6 @@ import java.util.Map; import java.util.TreeMap; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +39,6 @@ public class AndroidResourcesUtils { private AndroidResourcesUtils() { } - @Nullable public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) { String appPackage = root.getAppPackage(); String fullName = appPackage != null ? appPackage + ".R" : "R"; @@ -59,14 +57,10 @@ public class AndroidResourcesUtils { if (!candidates.isEmpty()) { LOG.info("Found several 'R' class candidates: {}", candidates); } - LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName); - resCls = makeClass(root, fullName, resStorage); - if (resCls == null) { - // We are in an APK without code therefore we don't have to update an 'R' class with the resources - return null; - } - addResourceFields(resCls, resStorage, false); - return resCls; + LOG.info("App 'R' class not found, put all resources ids into : '{}'", fullName); + ClassNode newResCls = makeClass(root, fullName, resStorage); + addResourceFields(newResCls, resStorage, false); + return newResCls; } public static boolean handleAppResField(CodeWriter code, ClassGen clsGen, ClassInfo declClass) { @@ -80,9 +74,8 @@ public class AndroidResourcesUtils { return false; } - @Nullable private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) { - ClassNode rCls = new ClassNode(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); + ClassNode rCls = ClassNode.addSyntheticClass(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL); rCls.addAttr(AType.COMMENTS, "This class is generated by JADX"); rCls.setState(ProcessState.PROCESS_COMPLETE); return rCls; @@ -131,11 +124,11 @@ public class AndroidResourcesUtils { @NotNull private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { - ClassNode newTypeCls = new ClassNode(resCls.root(), resCls.getFullName() + '$' + typeName, + ClassNode newTypeCls = ClassNode.addSyntheticClass(resCls.root(), resCls.getFullName() + '$' + typeName, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL); resCls.addInnerClass(newTypeCls); if (rClsExists) { - newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); + newTypeCls.addAttr(AType.COMMENTS, "Added by JADX"); } return newTypeCls; } diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java similarity index 94% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java index 7f01c2965..a7bb2f302 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldAccess.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldAccess.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import org.junit.jupiter.api.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 new file mode 100644 index 000000000..cc097bee2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore.java @@ -0,0 +1,63 @@ +package jadx.tests.integration.android; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.FieldInitAttr; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +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.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; + +public class TestRFieldRestore extends IntegrationTest { + + public static class TestCls { + public int test() { + return 2131230730; + } + } + + @Test + public void test() { + // unknown R class + disableCompilation(); + + Map map = new HashMap<>(); + int buttonConstValue = 2131230730; + map.put(buttonConstValue, "id.Button"); + setResMap(map); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + assertThat(code, containsOne("return R.id.Button;")); + assertThat(code, not(containsString("import R;"))); + + // check 'R' class + ClassNode rCls = cls.root().searchClassByFullAlias("R"); + assertThat(rCls, notNullValue()); + + // check inner 'id' class + List innerClasses = rCls.getInnerClasses(); + assertThat(innerClasses, hasSize(1)); + ClassNode idCls = innerClasses.get(0); + assertThat(idCls.getShortName(), is("id")); + + // check 'Button' field + FieldNode buttonField = idCls.searchFieldByName("Button"); + assertThat(buttonField, notNullValue()); + FieldInitAttr fieldInitAttr = buttonField.get(AType.FIELD_INIT); + Integer buttonValue = (Integer) fieldInitAttr.getEncodedValue().getValue(); + assertThat(buttonValue, is(buttonConstValue)); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java similarity index 94% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java index 4ea2d019c..a672b4ce9 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore2.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import java.util.HashMap; import java.util.Map; diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java similarity index 97% rename from jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java rename to jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java index 7fbca415b..1d9a5049e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/android/TestRFieldRestore3.java @@ -1,4 +1,4 @@ -package jadx.tests.integration.inner; +package jadx.tests.integration.android; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java deleted file mode 100644 index 785bb7875..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestRFieldRestore.java +++ /dev/null @@ -1,38 +0,0 @@ -package jadx.tests.integration.inner; - -import java.util.HashMap; -import java.util.Map; - -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; - -public class TestRFieldRestore extends IntegrationTest { - - public static class TestCls { - public int test() { - return 2131230730; - } - } - - @Test - public void test() { - // unknown R class - disableCompilation(); - - Map map = new HashMap<>(); - map.put(2131230730, "id.Button"); - setResMap(map); - - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - assertThat(code, containsOne("return R.id.Button;")); - assertThat(code, not(containsString("import R;"))); - } -} From a83ca1f85b240a79c045b2aa27785ef114c17ed0 Mon Sep 17 00:00:00 2001 From: Skylot Date: Wed, 10 Jun 2020 20:43:39 +0300 Subject: [PATCH 51/55] fix: don't use FileChannel on ZipFs to avoid creation of temp files (#950) --- .../jadx/plugins/input/dex/DexFileLoader.java | 40 +++++++++---------- .../jadx/plugins/input/dex/DexReader.java | 24 ++--------- .../plugins/input/dex/sections/DexConsts.java | 2 + 3 files changed, 23 insertions(+), 43 deletions(-) diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java index 925d250b9..72050f370 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexFileLoader.java @@ -1,8 +1,7 @@ package jadx.plugins.input.dex; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; +import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; @@ -12,7 +11,6 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -35,11 +33,15 @@ public class DexFileLoader { } private static List loadDexFromPath(Path path, int depth) { - try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { - if (isDex(fileChannel)) { - return Collections.singletonList(new DexReader(path, fileChannel)); + try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) { + byte[] magic = new byte[DexConsts.MAX_MAGIC_SIZE]; + if (inputStream.read(magic) != magic.length) { + return Collections.emptyList(); } - if (depth == 0 && isZip(fileChannel)) { + if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC)) { + return Collections.singletonList(new DexReader(path)); + } + if (depth == 0 && isStartWithBytes(magic, DexConsts.ZIP_FILE_MAGIC)) { return collectDexFromZip(path, depth); } } catch (Exception e) { @@ -64,22 +66,16 @@ public class DexFileLoader { return result; } - private static boolean isDex(FileChannel fileChannel) { - return isStartWithBytes(fileChannel, DexConsts.DEX_FILE_MAGIC); - } - - private static boolean isZip(FileChannel fileChannel) { - return isStartWithBytes(fileChannel, DexConsts.ZIP_FILE_MAGIC); - } - - private static boolean isStartWithBytes(FileChannel fileChannel, byte[] startBytes) { - try { - fileChannel.position(0); - ByteBuffer buf = ByteBuffer.allocate(startBytes.length); - fileChannel.read(buf); - return Arrays.equals(startBytes, buf.array()); - } catch (Exception e) { + private 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; } } diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java index 4ae196da0..14105047a 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/DexReader.java @@ -3,8 +3,8 @@ package jadx.plugins.input.dex; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.FileSystem; +import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Consumer; @@ -17,32 +17,15 @@ import jadx.plugins.input.dex.sections.annotations.AnnotationsParser; public class DexReader implements Closeable { private final Path path; - private final FileChannel fileChannel; private final ByteBuffer buf; private final DexHeader header; - public DexReader(Path path, FileChannel fileChannel) throws IOException { + public DexReader(Path path) throws IOException { this.path = path; - this.fileChannel = fileChannel; - this.buf = loadIntoByteBuffer(fileChannel); + this.buf = ByteBuffer.wrap(Files.readAllBytes(path)); this.header = new DexHeader(new SectionReader(this, 0)); } - private static ByteBuffer loadIntoByteBuffer(FileChannel fileChannel) throws IOException { - long size = fileChannel.size(); - if (size > Integer.MAX_VALUE) { - throw new IOException("File too big"); - } - int readSize = (int) size; - ByteBuffer buf = ByteBuffer.allocate(readSize); - fileChannel.position(0); - int read = fileChannel.read(buf); - if (read != readSize) { - throw new IOException("Failed to read whole file into buffer. Read: " + read + ", expected: " + readSize); - } - return buf; - } - public String getDexVersion() { return this.header.getVersion(); } @@ -86,7 +69,6 @@ public class DexReader implements Closeable { @Override public void close() throws IOException { - this.fileChannel.close(); } @Override diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java index 7323574fb..f249a4662 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexConsts.java @@ -6,6 +6,8 @@ public class DexConsts { public static final byte[] ZIP_FILE_MAGIC = { 0x50, 0x4B, 0x03, 0x04 }; + public static final int MAX_MAGIC_SIZE = 4; + public static final int ENDIAN_CONSTANT = 0x12345678; public static final int NO_INDEX = -1; From 4b1886700d4195e4a58f29f1843bfef29e8abaeb Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 11 Jun 2020 16:19:19 +0300 Subject: [PATCH 52/55] fix(gui): merge plugins service files (#949) Signed-off-by: Skylot --- jadx-gui/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 6946bdc50..95bf7c559 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -43,6 +43,10 @@ jar { } } +shadowJar { + mergeServiceFiles() +} + startScripts { // The option -XX:+UseG1GC is only relevant for Java 8. Starting with Java 9 G1GC is already the default GC defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC'] From 21b8552386f5770e91a133afa7c88315615001e9 Mon Sep 17 00:00:00 2001 From: Jan S Date: Thu, 11 Jun 2020 16:06:43 +0200 Subject: [PATCH 53/55] fix (gui): launch4j exe startup wrapper does not apply maximum heap configuration correctly (#951) --- jadx-gui/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 95bf7c559..34a135763 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -70,6 +70,7 @@ launch4j { jreMinVersion = '1.8.0' jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC'] jreRuntimeBits = "64" + bundledJre64Bit = true initialHeapPercent = 5 maxHeapSize = 4096 maxHeapPercent = 70 From f696dc715b9a49e562736c3039f06b90adcf766c Mon Sep 17 00:00:00 2001 From: Snowhite Date: Fri, 3 Jul 2020 22:19:18 +0800 Subject: [PATCH 54/55] fix(gui): update Messages_zh_CN.properties (PR #959) modifying error of positional argument in Chinese search result text --- jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 9e23bddc8..e9cfdc721 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -72,7 +72,7 @@ search_dialog.options=搜索选项: search_dialog.ignorecase=忽略大小写 search_dialog.next_page=下一页 search_dialog.prev_page=上一页 -search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %3$d 个 +search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个 search_dialog.col_node=节点 search_dialog.col_code=代码 From 99569c52ac66921865436d0fbb9f7eec012de3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EYasoob=20Ullah=20Khalid=20=E2=98=BA?= Date: Fri, 3 Jul 2020 10:22:00 -0400 Subject: [PATCH 55/55] docs: fixed grammar slightly (PR #957) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 963a1fced..fa6af6820 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ **jadx** - Dex to Java decompiler -Command line and GUI tools for produce Java source code from Android Dex and Apk files +Command line and GUI tools for producing Java source code from Android Dex and Apk files **Main features:** - decompile Dalvik bytecode to java classes from APK, dex, aar and zip files