feat: add support for 'package-info' (#1967)

This commit is contained in:
Skylot
2023-08-07 18:46:49 +01:00
parent f213082da5
commit c602b3d967
11 changed files with 94 additions and 8 deletions
@@ -249,7 +249,6 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public String getCodeStr() {
removeFirstEmptyLine();
return buf.toString();
}
@@ -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<ClassInfo> 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 {
@@ -44,6 +44,8 @@ public enum AFlag {
THIS,
SUPER,
PACKAGE_INFO,
/**
* Mark Android resources class
*/
@@ -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);
@@ -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);
}
@@ -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();
@@ -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<? super JavaFileObject> diagnostic =
diagObj -> System.out.println("Compiler diagnostic: " + diagObj);
SimpleCodeWriter output = new SimpleCodeWriter();
DiagnosticListener<JavaFileObject> 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);
}
}
@@ -32,7 +32,6 @@ public class TestArrayAccessReorder extends IntegrationTest {
@Test
public void test() {
noDebugInfo();
getArgs().setRawCFGOutput(true);
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("i++");
@@ -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<ClassNode> 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;");
}
}
@@ -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
@@ -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