From ecdc4e6757facb97dfe6ee50f367fc3cb90604bb Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 30 Mar 2024 21:51:02 +0000 Subject: [PATCH] refactor: move constant collection into separate pass (#2119) --- jadx-core/src/main/java/jadx/core/Jadx.java | 2 + .../java/jadx/core/dex/info/ConstStorage.java | 29 ++-------- .../java/jadx/core/dex/nodes/ClassNode.java | 19 +++---- .../visitors/prepare/CollectConstValues.java | 55 +++++++++++++++++++ .../java/jadx/gui/ui/dialog/UsageDialog.java | 6 +- 5 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/prepare/CollectConstValues.java diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 4da023c84..c6aa752a6 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -54,6 +54,7 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor; import jadx.core.dex.visitors.finaly.MarkFinallyVisitor; import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals; +import jadx.core.dex.visitors.prepare.CollectConstValues; import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.IfRegionVisitor; @@ -96,6 +97,7 @@ public class Jadx { List passes = new ArrayList<>(); passes.add(new SignatureProcessor()); passes.add(new OverrideMethodVisitor()); + passes.add(new CollectConstValues()); // rename and deobfuscation passes.add(new DeobfuscatorVisitor()); 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 826432a8c..75b06a3d9 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 @@ -2,7 +2,6 @@ package jadx.core.dex.info; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -10,14 +9,13 @@ import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.Nullable; import jadx.api.JadxArgs; -import jadx.api.plugins.input.data.annotations.EncodedValue; -import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.IFieldInfoRef; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.prepare.CollectConstValues; public class ConstStorage { @@ -75,18 +73,6 @@ public class ConstStorage { this.replaceEnabled = args.isReplaceConsts(); } - public void processConstFields(List staticFields) { - if (!replaceEnabled || staticFields.isEmpty()) { - return; - } - for (FieldNode f : staticFields) { - Object value = getFieldConstValue(f); - if (value != null) { - addConstField(f, value, f.getAccessFlags().isPublic()); - } - } - } - public void addConstField(FieldNode fld, Object value, boolean isPublic) { if (isPublic) { addGlobalConstField(fld, value); @@ -99,15 +85,12 @@ public class ConstStorage { globalValues.put(value, fld); } + /** + * Use method from CollectConstValues class + */ + @Deprecated public static @Nullable Object getFieldConstValue(FieldNode fld) { - AccessInfo accFlags = fld.getAccessFlags(); - if (accFlags.isStatic() && accFlags.isFinal()) { - EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE); - if (constVal != null) { - return constVal.getValue(); - } - } - return null; + return CollectConstValues.getFieldConstValue(fld); } public void removeForClass(ClassNode cls) { 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 1c401fbe7..c6a9e01c8 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 @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -248,19 +247,15 @@ public class ClassNode extends NotificationAttrNode if (fields.isEmpty()) { return; } - List staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList()); - for (FieldNode f : staticFields) { - if (f.getAccessFlags().isFinal() && f.get(JadxAttrType.CONSTANT_VALUE) == null) { - // incorrect initialization will be removed if assign found in constructor - f.addAttr(EncodedValue.NULL); + // bytecode can omit field initialization to 0 (of any type) + // add explicit init to all static final fields + // incorrect initializations will be removed if assign found in class init + for (FieldNode fld : fields) { + AccessInfo accFlags = fld.getAccessFlags(); + if (accFlags.isStatic() && accFlags.isFinal() && fld.get(JadxAttrType.CONSTANT_VALUE) == null) { + fld.addAttr(EncodedValue.NULL); } } - try { - // process const fields - root().getConstValues().processConstFields(staticFields); - } catch (Exception e) { - this.addWarnComment("Failed to load initial values for static fields", e); - } } private boolean checkSourceFilenameAttr() { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/CollectConstValues.java b/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/CollectConstValues.java new file mode 100644 index 000000000..06efe76ad --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/prepare/CollectConstValues.java @@ -0,0 +1,55 @@ +package jadx.core.dex.visitors.prepare; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.plugins.input.data.annotations.EncodedValue; +import jadx.api.plugins.input.data.attributes.JadxAttrType; +import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.info.ConstStorage; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.utils.exceptions.JadxException; + +@JadxVisitor( + name = "CollectConstValues", + desc = "Collect and store values from static final fields" +) +public class CollectConstValues extends AbstractVisitor { + + @Override + public boolean visit(ClassNode cls) throws JadxException { + RootNode root = cls.root(); + if (!root.getArgs().isReplaceConsts()) { + return true; + } + if (cls.getFields().isEmpty()) { + return true; + } + ConstStorage constStorage = root.getConstValues(); + for (FieldNode fld : cls.getFields()) { + try { + Object value = getFieldConstValue(fld); + if (value != null) { + constStorage.addConstField(fld, value, fld.getAccessFlags().isPublic()); + } + } catch (Exception e) { + cls.addWarnComment("Failed to process value of field: " + fld, e); + } + } + return true; + } + + public static @Nullable Object getFieldConstValue(FieldNode fld) { + AccessInfo accFlags = fld.getAccessFlags(); + if (accFlags.isStatic() && accFlags.isFinal()) { + EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE); + if (constVal != null && constVal != EncodedValue.NULL) { + return constVal.getValue(); + } + } + return null; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java index 7ccf14810..5155c4a60 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java @@ -19,8 +19,8 @@ import jadx.api.JavaClass; import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.api.utils.CodeUtils; -import jadx.core.dex.info.ConstStorage; import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.visitors.prepare.CollectConstValues; import jadx.gui.JadxWrapper; import jadx.gui.jobs.TaskStatus; import jadx.gui.settings.JadxSettings; @@ -70,7 +70,7 @@ public class UsageDialog extends CommonSearchDialog { private void prepareUsageData() { if (mainWindow.getSettings().isReplaceConsts() && node instanceof JField) { FieldNode fld = ((JField) node).getJavaField().getFieldNode(); - boolean constField = ConstStorage.getFieldConstValue(fld) != null; + boolean constField = CollectConstValues.getFieldConstValue(fld) != null; if (constField && !fld.getAccessFlags().isPrivate()) { // run full decompilation to prepare for full code scan mainWindow.requestFullDecompilation(); @@ -112,7 +112,7 @@ public class UsageDialog extends CommonSearchDialog { } if (node instanceof JField && mainWindow.getSettings().isReplaceConsts()) { FieldNode fld = ((JField) node).getJavaField().getFieldNode(); - boolean constField = ConstStorage.getFieldConstValue(fld) != null; + boolean constField = CollectConstValues.getFieldConstValue(fld) != null; if (constField && !fld.getAccessFlags().isPrivate()) { // search all classes to collect usage of replaced constants map.put(fld.getJavaNode(), mainWindow.getWrapper().getIncludedClasses());