From c602b3d967d89cf222bd73bafa58c3e2116fc190 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 7 Aug 2023 18:46:49 +0100 Subject: [PATCH] feat: add support for 'package-info' (#1967) --- .../java/jadx/api/impl/SimpleCodeWriter.java | 1 - .../main/java/jadx/core/codegen/ClassGen.java | 28 ++++++++++++++-- .../java/jadx/core/dex/attributes/AFlag.java | 2 ++ .../java/jadx/core/dex/nodes/ClassNode.java | 10 ++++++ .../jadx/core/dex/visitors/ClassModifier.java | 3 ++ .../dex/visitors/rename/RenameVisitor.java | 3 ++ .../jadx/tests/api/compiler/TestCompiler.java | 11 +++++-- .../code/TestArrayAccessReorder.java | 1 - .../special/TestPackageInfoSupport.java | 32 +++++++++++++++++++ .../special/TestPackageInfoSupport/pkg1.smali | 6 ++++ .../special/TestPackageInfoSupport/pkg2.smali | 5 +++ 11 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/special/TestPackageInfoSupport.java create mode 100644 jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg1.smali create mode 100644 jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg2.smali diff --git a/jadx-core/src/main/java/jadx/api/impl/SimpleCodeWriter.java b/jadx-core/src/main/java/jadx/api/impl/SimpleCodeWriter.java index 6f12e1485..ed9a0e2fa 100644 --- a/jadx-core/src/main/java/jadx/api/impl/SimpleCodeWriter.java +++ b/jadx-core/src/main/java/jadx/api/impl/SimpleCodeWriter.java @@ -249,7 +249,6 @@ public class SimpleCodeWriter implements ICodeWriter { @Override public String getCodeStr() { - removeFirstEmptyLine(); return buf.toString(); } 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 ddf0c93dd..6c2b8373f 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -96,16 +96,29 @@ public class ClassGen { } public ICodeInfo makeClass() throws CodegenException { + if (cls.contains(AFlag.PACKAGE_INFO)) { + return makePackageInfo(); + } ICodeWriter clsBody = cls.root().makeCodeWriter(); addClassCode(clsBody); ICodeWriter clsCode = cls.root().makeCodeWriter(); + addPackage(clsCode); + clsCode.newLine(); + addImports(clsCode); + clsCode.add(clsBody); + return clsCode.finish(); + } + + private void addPackage(ICodeWriter clsCode) { if (cls.getPackage().isEmpty()) { clsCode.add("// default package"); } else { clsCode.add("package ").add(cls.getPackage()).add(';'); } - clsCode.newLine(); + } + + private void addImports(ICodeWriter clsCode) { int importsCount = imports.size(); if (importsCount != 0) { List sortedImports = new ArrayList<>(imports); @@ -122,8 +135,17 @@ public class ClassGen { clsCode.newLine(); imports.clear(); } - clsCode.add(clsBody); - return clsCode.finish(); + } + + private ICodeInfo makePackageInfo() { + ICodeWriter code = cls.root().makeCodeWriter(); + annotationGen.addForClass(code); + code.newLine(); + code.attachDefinition(cls); + addPackage(code); + code.newLine(); + addImports(code); + return code.finish(); } public void addClassCode(ICodeWriter code) throws CodegenException { 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 75e802b7c..76ba20743 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 @@ -44,6 +44,8 @@ public enum AFlag { THIS, SUPER, + PACKAGE_INFO, + /** * Mark Android resources class */ 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 07f75dabb..e97f6fdb2 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 @@ -127,6 +127,7 @@ public class ClassNode extends NotificationAttrNode } initStaticValues(fields); processAttributes(this); + processSpecialClasses(this); buildCache(); // TODO: implement module attribute parsing @@ -167,6 +168,15 @@ public class ClassNode extends NotificationAttrNode this.interfaces = interfaces; } + private static void processSpecialClasses(ClassNode cls) { + AccessInfo flags = cls.getAccessFlags(); + if (flags.isSynthetic() && flags.isInterface() && flags.isAbstract() + && cls.getName().equals("package-info")) { + cls.add(AFlag.PACKAGE_INFO); + cls.add(AFlag.DONT_RENAME); + } + } + private static void processAttributes(ClassNode cls) { // move AnnotationDefault from cls to methods (dex specific) AnnotationDefaultClassAttr defAttr = cls.get(JadxAttrType.ANNOTATION_DEFAULT_CLASS); 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 3d82cf408..c286aced1 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 @@ -50,6 +50,9 @@ public class ClassModifier extends AbstractVisitor { @Override public boolean visit(ClassNode cls) throws JadxException { + if (cls.contains(AFlag.PACKAGE_INFO)) { + return false; + } for (ClassNode inner : cls.getInnerClasses()) { visit(inner); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java index 68a5b7113..331ef82b2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/rename/RenameVisitor.java @@ -81,6 +81,9 @@ public class RenameVisitor extends AbstractVisitor { } private static void checkClassName(IAliasProvider aliasProvider, ClassNode cls, JadxArgs args) { + if (cls.contains(AFlag.DONT_RENAME)) { + return; + } ClassInfo classInfo = cls.getClassInfo(); String clsName = classInfo.getAliasShortName(); diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java b/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java index 15d9acfc6..cb16d2a55 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/TestCompiler.java @@ -20,6 +20,7 @@ import javax.tools.ToolProvider; import org.jetbrains.annotations.NotNull; +import jadx.api.impl.SimpleCodeWriter; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.files.FileUtils; import jadx.tests.api.IntegrationTest; @@ -80,12 +81,16 @@ public class TestCompiler implements Closeable { arguments.add(javaVerStr); arguments.addAll(options.getArguments()); - DiagnosticListener diagnostic = - diagObj -> System.out.println("Compiler diagnostic: " + diagObj); + SimpleCodeWriter output = new SimpleCodeWriter(); + DiagnosticListener diagnostic = diagObj -> { + String msg = "Compiler diagnostic: " + diagObj; + output.startLine(msg); + System.out.println(msg); + }; Writer out = new PrintWriter(System.out); CompilationTask compilerTask = compiler.getTask(out, fileManager, diagnostic, arguments, null, jfObjects); if (Boolean.FALSE.equals(compilerTask.call())) { - throw new RuntimeException("Compilation failed"); + throw new RuntimeException("Compilation failed: " + output); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/code/TestArrayAccessReorder.java b/jadx-core/src/test/java/jadx/tests/integration/code/TestArrayAccessReorder.java index 67c165584..0e08dad9b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/code/TestArrayAccessReorder.java +++ b/jadx-core/src/test/java/jadx/tests/integration/code/TestArrayAccessReorder.java @@ -32,7 +32,6 @@ public class TestArrayAccessReorder extends IntegrationTest { @Test public void test() { noDebugInfo(); - getArgs().setRawCFGOutput(true); assertThat(getClassNode(TestCls.class)) .code() .containsOne("i++"); diff --git a/jadx-core/src/test/java/jadx/tests/integration/special/TestPackageInfoSupport.java b/jadx-core/src/test/java/jadx/tests/integration/special/TestPackageInfoSupport.java new file mode 100644 index 000000000..4b181ebc6 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/special/TestPackageInfoSupport.java @@ -0,0 +1,32 @@ +package jadx.tests.integration.special; + +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 TestPackageInfoSupport extends SmaliTest { + + @Test + public void test() { + disableCompilation(); + List classes = loadFromSmaliFiles(); + assertThat(searchCls(classes, "special.pkg1.package-info")) + .satisfies(cls -> assertThat(cls.getAlias()).isEqualTo("package-info")) // shouldn't be renamed + .code() + .containsLines( + "@Deprecated", + "package special.pkg1;"); + assertThat(searchCls(classes, "special.pkg2.package-info")) + .code() + .containsLines( + "@ApiStatus.Internal", + "package special.pkg2;", + "", + "import org.jetbrains.annotations.ApiStatus;"); + } +} diff --git a/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg1.smali b/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg1.smali new file mode 100644 index 000000000..09f585c0a --- /dev/null +++ b/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg1.smali @@ -0,0 +1,6 @@ +.class interface abstract synthetic Lspecial/pkg1/package-info; +.super Ljava/lang/Object; +.source "package-info.java" + +.annotation runtime Ljava/lang/Deprecated; +.end annotation diff --git a/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg2.smali b/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg2.smali new file mode 100644 index 000000000..7534cb1b7 --- /dev/null +++ b/jadx-core/src/test/smali/special/TestPackageInfoSupport/pkg2.smali @@ -0,0 +1,5 @@ +.class interface abstract synthetic Lspecial/pkg2/package-info; +.super Ljava/lang/Object; + +.annotation runtime Lorg/jetbrains/annotations/ApiStatus$Internal; +.end annotation