-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(gradle): handle non-final res ids for AGP >8.0.0 (PR #2362)
- Loading branch information
Showing
6 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
jadx-core/src/main/java/jadx/core/dex/visitors/gradle/NonFinalResIdsVisitor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package jadx.core.dex.visitors.gradle; | ||
|
||
import java.util.Map; | ||
|
||
import jadx.api.plugins.input.data.annotations.AnnotationVisibility; | ||
import jadx.api.plugins.input.data.annotations.EncodedValue; | ||
import jadx.api.plugins.input.data.annotations.IAnnotation; | ||
import jadx.api.plugins.input.data.attributes.JadxAttrType; | ||
import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr; | ||
import jadx.core.dex.attributes.nodes.CodeFeaturesAttr; | ||
import jadx.core.dex.info.ClassInfo; | ||
import jadx.core.dex.nodes.ClassNode; | ||
import jadx.core.dex.nodes.FieldNode; | ||
import jadx.core.dex.nodes.IFieldInfoRef; | ||
import jadx.core.dex.nodes.IRegion; | ||
import jadx.core.dex.nodes.MethodNode; | ||
import jadx.core.dex.nodes.RootNode; | ||
import jadx.core.dex.regions.SwitchRegion; | ||
import jadx.core.dex.visitors.AbstractVisitor; | ||
import jadx.core.dex.visitors.FixSwitchOverEnum; | ||
import jadx.core.dex.visitors.JadxVisitor; | ||
import jadx.core.dex.visitors.regions.DepthRegionTraversal; | ||
import jadx.core.dex.visitors.regions.IRegionIterativeVisitor; | ||
import jadx.core.export.GradleInfoStorage; | ||
import jadx.core.utils.android.AndroidResourcesUtils; | ||
import jadx.core.utils.exceptions.JadxException; | ||
|
||
@JadxVisitor( | ||
name = "NonFinalResIdsVisitor", | ||
desc = "Detect usage of android resource constants in cases where constant expressions are required.", | ||
runAfter = FixSwitchOverEnum.class | ||
) | ||
public class NonFinalResIdsVisitor extends AbstractVisitor implements IRegionIterativeVisitor { | ||
|
||
private boolean nonFinalResIdsFlagRequired = false; | ||
|
||
private GradleInfoStorage gradleInfoStorage; | ||
|
||
public void init(RootNode root) throws JadxException { | ||
gradleInfoStorage = root.getGradleInfoStorage(); | ||
} | ||
|
||
@Override | ||
public boolean visit(ClassNode cls) throws JadxException { | ||
if (nonFinalResIdsFlagRequired) { | ||
return false; | ||
} | ||
AnnotationsAttr annotationsList = cls.get(JadxAttrType.ANNOTATION_LIST); | ||
if (visitAnnotationList(annotationsList)) { | ||
return false; | ||
} | ||
return super.visit(cls); | ||
} | ||
|
||
private static boolean isCustomResourceClass(ClassInfo cls) { | ||
ClassInfo parentClass = cls.getParentClass(); | ||
return parentClass != null && parentClass.getShortName().equals("R") && !parentClass.getFullName().equals("android.R"); | ||
} | ||
|
||
@Override | ||
public void visit(MethodNode mth) throws JadxException { | ||
AnnotationsAttr annotationsList = mth.get(JadxAttrType.ANNOTATION_LIST); | ||
if (visitAnnotationList(annotationsList)) { | ||
nonFinalResIdsFlagRequired = true; | ||
return; | ||
} | ||
|
||
if (nonFinalResIdsFlagRequired || !CodeFeaturesAttr.contains(mth, CodeFeaturesAttr.CodeFeature.SWITCH)) { | ||
return; | ||
} | ||
DepthRegionTraversal.traverseIterative(mth, this); | ||
} | ||
|
||
private boolean visitAnnotationList(AnnotationsAttr annotationsList) { | ||
if (annotationsList != null) { | ||
for (IAnnotation annotation : annotationsList.getAll()) { | ||
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) { | ||
continue; | ||
} | ||
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) { | ||
Object value = entry.getValue().getValue(); | ||
if (value instanceof IFieldInfoRef && isCustomResourceClass(((IFieldInfoRef) value).getFieldInfo().getDeclClass())) { | ||
gradleInfoStorage.setNonFinalResIds(true); | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean visitRegion(MethodNode mth, IRegion region) { | ||
if (nonFinalResIdsFlagRequired) { | ||
return false; | ||
} | ||
if (region instanceof SwitchRegion) { | ||
return detectSwitchOverResIds((SwitchRegion) region); | ||
} | ||
return false; | ||
} | ||
|
||
private boolean detectSwitchOverResIds(SwitchRegion switchRegion) { | ||
for (SwitchRegion.CaseInfo caseInfo : switchRegion.getCases()) { | ||
for (Object key : caseInfo.getKeys()) { | ||
if (key instanceof FieldNode) { | ||
ClassNode topParentClass = ((FieldNode) key).getTopParentClass(); | ||
if (AndroidResourcesUtils.isResourceClass(topParentClass) && !"android.R".equals(topParentClass.getFullName())) { | ||
this.nonFinalResIdsFlagRequired = true; | ||
gradleInfoStorage.setNonFinalResIds(true); | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
jadx-core/src/test/java/jadx/tests/export/TestNonFinalResIds.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package jadx.tests.export; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import jadx.core.export.GradleInfoStorage; | ||
import jadx.tests.api.ExportGradleTest; | ||
|
||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; | ||
|
||
public class TestNonFinalResIds extends ExportGradleTest { | ||
|
||
@Test | ||
void test() { | ||
GradleInfoStorage gradleInfo = getRootNode().getGradleInfoStorage(); | ||
gradleInfo.setNonFinalResIds(false); | ||
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml"); | ||
Assertions.assertFalse(getGradleProperiesFile().exists()); | ||
|
||
gradleInfo.setNonFinalResIds(true); | ||
exportGradle("OptionalTargetSdkVersion.xml", "strings.xml"); | ||
assertThat(getGradleProperies()).containsOne("android.nonFinalResIds=false"); | ||
} | ||
} |