fix: deep reload for inner classes, const values and anonymous classes

This commit is contained in:
Skylot
2020-01-05 17:28:25 +03:00
parent 0c4b807caa
commit 9dbffef140
7 changed files with 157 additions and 19 deletions
@@ -66,9 +66,13 @@ public final class ClassInfo implements Comparable<ClassInfo> {
}
public void changeShortName(String aliasName) {
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
fillAliasFullName(newAlias);
this.alias = newAlias;
if (!Objects.equals(name, aliasName)) {
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
fillAliasFullName(newAlias);
this.alias = newAlias;
} else {
this.alias = null;
}
}
public void changePkg(String aliasPkg) {
@@ -2,8 +2,10 @@ package jadx.core.dex.info;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
@@ -52,6 +54,18 @@ public class ConstStorage {
public boolean contains(Object value) {
return duplicates.contains(value) || values.containsKey(value);
}
void removeForCls(ClassNode cls) {
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, FieldNode> entry = it.next();
FieldNode field = entry.getValue();
if (field.getParentClass().equals(cls)) {
it.remove();
duplicates.remove(entry.getKey());
}
}
}
}
private final boolean replaceEnabled;
@@ -82,6 +96,11 @@ public class ConstStorage {
}
}
public void removeForClass(ClassNode cls) {
classes.remove(cls);
globalValues.removeForCls(cls);
}
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
globalValues.put(value, fld);
@@ -35,6 +35,7 @@ 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.dex.visitors.ProcessAnonymous;
import jadx.core.utils.SmaliUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -175,9 +176,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
for (FieldNode f : staticFields) {
AccessInfo flags = f.getAccessFlags();
if (flags.isStatic() && flags.isFinal()) {
LOG.debug("loadStaticValues(): Adding NULL initializer to static final field {}", f.getAlias());
if (f.getAccessFlags().isFinal()) {
f.addAttr(FieldInitAttr.NULL_VALUE);
}
}
@@ -281,12 +280,21 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
public synchronized ICodeInfo reloadCode() {
unload();
clearAttributes();
initialLoad();
load();
deepUnload();
return decompile(false);
}
private void deepUnload() {
clearAttributes();
root().getConstValues().removeForClass(this);
initialLoad();
ProcessAnonymous.runForClass(this);
for (ClassNode innerClass : innerClasses) {
innerClass.deepUnload();
}
}
private synchronized ICodeInfo decompile(boolean searchInCache) {
ICodeCache codeCache = root().getCodeCache();
ClassNode topParentClass = getTopParentClass();
@@ -5,27 +5,29 @@ 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.regions.RegionMakerVisitor;
@JadxVisitor(
name = "ProcessAnonymous",
desc = "Mark anonymous and lambda classes (for future inline)",
runAfter = RegionMakerVisitor.class
desc = "Mark anonymous and lambda classes (for future inline)"
)
public class ProcessAnonymous extends AbstractVisitor {
@Override
public void init(RootNode root) {
if (!root.getArgs().isInlineAnonymousClasses()) {
return;
if (root.getArgs().isInlineAnonymousClasses()) {
for (ClassNode cls : root.getClasses(true)) {
markAnonymousClass(cls);
}
}
}
for (ClassNode cls : root.getClasses(true)) {
public static void runForClass(ClassNode cls) {
if (cls.root().getArgs().isInlineAnonymousClasses()) {
markAnonymousClass(cls);
}
}
private static boolean markAnonymousClass(ClassNode cls) {
private static void markAnonymousClass(ClassNode cls) {
if (isAnonymous(cls) || isLambdaCls(cls)) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
@@ -35,9 +37,7 @@ public class ProcessAnonymous extends AbstractVisitor {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
return true;
}
return false;
}
private static boolean isAnonymous(ClassNode cls) {
@@ -62,5 +62,4 @@ public class ProcessAnonymous extends AbstractVisitor {
}
return c;
}
}
@@ -0,0 +1,34 @@
package jadx.tests.integration.rename;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestAnonymousInline extends IntegrationTest {
public static class TestCls {
public Runnable test() {
return new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
assertThat(cls.getCode())
.containsOnlyOnce("return new Runnable() {");
assertThat(cls.reloadCode())
.print()
.containsOnlyOnce("return new Runnable() {")
.doesNotContain("AnonymousClass1");
}
}
@@ -0,0 +1,30 @@
package jadx.tests.integration.rename;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestConstReplace extends IntegrationTest {
public static class TestCls {
public static final String CONST = "SOME_CONST";
public String test() {
return CONST;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
assertThat(cls.getCode())
.containsOnlyOnce("return CONST;");
assertThat(cls.reloadCode())
.print()
.containsOnlyOnce("return CONST;");
}
}
@@ -0,0 +1,44 @@
package jadx.tests.integration.rename;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestRenameEnum extends IntegrationTest {
public static class TestCls {
public enum A implements Runnable {
ONE {
@Override
public void run() {
System.out.println("ONE");
}
},
TWO {
@Override
public void run() {
System.out.println("TWO");
}
};
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
assertThat(cls.getCode())
.containsOnlyOnce("public enum A ")
.containsOnlyOnce("ONE {");
cls.getInnerClasses().get(0).getClassInfo().changeShortName("ARenamed");
assertThat(cls.reloadCode())
.print()
.containsOnlyOnce("public enum ARenamed ")
.containsOnlyOnce("ONE {");
}
}