From c9d650d186e508016a5897f753eaf88101b3214b Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:36:46 +0000 Subject: [PATCH] fix: unload attributes map if empty (#2433) --- .gitignore | 1 + .../jadx/core/dex/attributes/AttrNode.java | 5 ++- .../core/dex/attributes/AttributeStorage.java | 35 +++++++++++-------- .../core/dex/attributes/EmptyAttrStorage.java | 6 ++++ .../jadx/gui/device/debugger/smali/Smali.java | 13 ++++--- .../input/data/attributes/IJadxAttribute.java | 5 ++- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index e93917c2a..bb51a528f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ jadx-output/ *.jadx *.class +*.jar *.dump *.log *.cfg 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 e5964179c..ffc1e8b4d 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 @@ -12,7 +12,7 @@ import jadx.core.utils.Utils; public abstract class AttrNode implements IAttributeNode { - private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage(); + private static final AttributeStorage EMPTY_ATTR_STORAGE = EmptyAttrStorage.INSTANCE; private AttributeStorage storage = EMPTY_ATTR_STORAGE; @@ -35,6 +35,9 @@ public abstract class AttrNode implements IAttributeNode { @Override public void addAttrs(List list) { + if (list.isEmpty()) { + return; + } initStorage().add(list); } 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 a3bbb860e..049dfd105 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 @@ -18,12 +18,19 @@ import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; /** - * Storage for different attribute types: - * 1. flags - boolean attribute (set or not) - * 2. attribute - class instance associated with attribute type. + * Storage for different attribute types:
+ * 1. Flags - boolean attribute (set or not)
+ * 2. Attributes - class instance ({@link IJadxAttribute}) associated with an attribute type + * ({@link IJadxAttrType})
*/ public class AttributeStorage { + public static AttributeStorage fromList(List list) { + AttributeStorage storage = new AttributeStorage(); + storage.add(list); + return storage; + } + static { int flagsCount = AFlag.values().length; if (flagsCount >= 64) { @@ -31,17 +38,14 @@ public class AttributeStorage { } } + private static final Map, IJadxAttribute> EMPTY_ATTRIBUTES = Collections.emptyMap(); + private final Set flags; private Map, IJadxAttribute> attributes; public AttributeStorage() { flags = EnumSet.noneOf(AFlag.class); - attributes = Collections.emptyMap(); - } - - public AttributeStorage(List attributesList) { - this(); - add(attributesList); + attributes = EMPTY_ATTRIBUTES; } public void add(AFlag flag) { @@ -125,11 +129,14 @@ public class AttributeStorage { } private void writeAttributes(Consumer, IJadxAttribute>> mapConsumer) { - if (attributes.isEmpty()) { - attributes = new IdentityHashMap<>(5); - } synchronized (this) { + if (attributes == EMPTY_ATTRIBUTES) { + attributes = new IdentityHashMap<>(2); // only 1 or 2 attributes added in most cases + } mapConsumer.accept(attributes); + if (attributes.isEmpty()) { + attributes = EMPTY_ATTRIBUTES; + } } } @@ -137,9 +144,7 @@ public class AttributeStorage { if (attributes.isEmpty()) { return; } - synchronized (this) { - attributes.entrySet().removeIf(entry -> !entry.getValue().keepLoaded()); - } + writeAttributes(map -> map.entrySet().removeIf(entry -> !entry.getValue().keepLoaded())); } public List getAttributeStrings() { 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 ec854541d..d3c63e4e2 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 @@ -9,6 +9,12 @@ import jadx.api.plugins.input.data.attributes.IJadxAttribute; public final class EmptyAttrStorage extends AttributeStorage { + public static final AttributeStorage INSTANCE = new EmptyAttrStorage(); + + private EmptyAttrStorage() { + // singleton + } + @Override public boolean contains(AFlag flag) { return false; diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java index 69854aa8c..4d4df33ed 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java @@ -185,16 +185,14 @@ public class Smali { smali.startLine(String.format("###### Class %s is created by jadx", cls.getFullName())); return; } - AttributeStorage attributes = new AttributeStorage(); - attributes.add(clsData.getAttributes()); - + AttributeStorage clsAttributes = AttributeStorage.fromList(clsData.getAttributes()); smali.startLine("Class: " + clsData.getType()) .startLine("AccessFlags: " + AccessFlags.format(clsData.getAccessFlags(), AccessFlagsScope.CLASS)) .startLine("SuperType: " + clsData.getSuperType()) .startLine("Interfaces: " + clsData.getInterfacesTypes()) - .startLine("SourceFile: " + attributes.get(JadxAttrType.SOURCE_FILE)); + .startLine("SourceFile: " + clsAttributes.get(JadxAttrType.SOURCE_FILE)); - AnnotationsAttr annotationsAttr = attributes.get(JadxAttrType.ANNOTATION_LIST); + AnnotationsAttr annotationsAttr = clsAttributes.get(JadxAttrType.ANNOTATION_LIST); if (annotationsAttr != null) { Collection annos = annotationsAttr.getList(); if (!annos.isEmpty()) { @@ -451,7 +449,8 @@ public class Smali { smali.add(')'); smali.add(methodRef.getReturnType()); - AnnotationsAttr annotationsAttr = new AttributeStorage(mth.getAttributes()).get(JadxAttrType.ANNOTATION_LIST); + AttributeStorage mthAttributes = AttributeStorage.fromList(mth.getAttributes()); + AnnotationsAttr annotationsAttr = mthAttributes.get(JadxAttrType.ANNOTATION_LIST); if (annotationsAttr != null && !annotationsAttr.isEmpty()) { smali.incIndent(); writeAnnotations(smali, annotationsAttr.getList()); @@ -1074,7 +1073,7 @@ public class Smali { field.accessFlag = AccessFlags.format(f.getAccessFlags(), FIELD); field.name = f.getName(); field.type = f.getType(); - field.attributes = new AttributeStorage(f.getAttributes()); + field.attributes = AttributeStorage.fromList(f.getAttributes()); return field; } } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java index 71f17138c..2dc7c545b 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/IJadxAttribute.java @@ -1,11 +1,14 @@ package jadx.api.plugins.input.data.attributes; +/** + * Jadx attribute: custom data container, can be added to most jadx nodes. + */ public interface IJadxAttribute { IJadxAttrType getAttrType(); /** - * Mark type to skip unloading on node unload + * Mark type to skip unloading on node unload event */ default boolean keepLoaded() { return false;