Merge branch 'master' into type-inference-wip

# Conflicts:
#	jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
#	jadx-core/src/main/java/jadx/core/Jadx.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java
#	jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java
#	jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
#	jadx-core/src/test/java/jadx/tests/functional/TypeMergeTest.java
#	jadx-core/src/test/java/jadx/tests/integration/TestFloatValue.java
#	jadx-core/src/test/java/jadx/tests/integration/TestStringBuilderElimination2.java
#	jadx-core/src/test/java/jadx/tests/integration/arith/TestArith.java
#	jadx-core/src/test/java/jadx/tests/integration/arith/TestFieldIncrement2.java
#	jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays3.java
#	jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrays4.java
#	jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary2.java
#	jadx-core/src/test/java/jadx/tests/integration/debuginfo/TestReturnSourceLine.java
#	jadx-core/src/test/java/jadx/tests/integration/generics/TestGenerics2.java
#	jadx-core/src/test/java/jadx/tests/integration/inline/TestInlineInLoop.java
#	jadx-core/src/test/java/jadx/tests/integration/invoke/TestCastInOverloadedInvoke.java
#	jadx-core/src/test/java/jadx/tests/integration/loops/TestArrayForEach2.java
#	jadx-core/src/test/java/jadx/tests/integration/loops/TestIndexForLoop.java
#	jadx-core/src/test/java/jadx/tests/integration/names/TestNameAssign2.java
#	jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchBreak.java
#	jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java
#	jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally8.java
This commit is contained in:
Skylot
2019-03-24 12:19:19 +03:00
377 changed files with 2958 additions and 1588 deletions
+23 -16
View File
@@ -1,25 +1,32 @@
image: java:8
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
before_script:
- chmod +x gradlew
- chmod +x gradlew
stages:
- build
- test
- check
build:
stage: build
before_script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-$(git rev-parse --short HEAD)"
java-8:
stage: test
image: openjdk:8
script: ./gradlew clean build
java-11:
stage: test
image: openjdk:11
script: ./gradlew clean build
check:
stage: check
image: openjdk:8
script:
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
- ./gradlew clean dist
artifacts:
paths:
- build/jadx*.zip
- build/jadx*.exe
- build/jadx*.zip
+8 -3
View File
@@ -38,10 +38,15 @@ allprojects {
compile 'org.slf4j:slf4j-api:1.7.26'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.25.0'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
testCompile 'org.mockito:mockito-core:2.25.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
}
test {
useJUnitPlatform()
}
repositories {
@@ -47,6 +47,9 @@ public class JadxCLIArgs {
@Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name")
protected boolean useImports = true;
@Parameter(names = {"--no-debug-info"}, description = "disable debug info")
protected boolean debugInfo = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@@ -160,6 +163,7 @@ public class JadxCLIArgs {
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
args.setDebugInfo(debugInfo);
return args;
}
@@ -203,6 +207,10 @@ public class JadxCLIArgs {
return useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@@ -1,11 +1,11 @@
package jadx.cli;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
public class JadxCLIArgsTest {
@@ -33,11 +33,14 @@ public class JadxCLIArgsTest {
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args.debugInfo = false;
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
@@ -27,6 +27,7 @@ public class JadxArgs {
private boolean showInconsistentCode = false;
private boolean useImports = true;
private boolean debugInfo = true;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
@@ -133,6 +134,14 @@ public class JadxArgs {
this.useImports = useImports;
}
public boolean isDebugInfo() {
return debugInfo;
}
public void setDebugInfo(boolean debugInfo) {
this.debugInfo = debugInfo;
}
public boolean isSkipResources() {
return isSkipResources;
}
@@ -77,7 +77,7 @@ public class JadxArgsValidator {
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
outDirName = name + '-' + JadxArgs.DEFAULT_OUT_DIR;
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
@@ -21,7 +21,7 @@ public final class JavaField implements JavaNode {
@Override
public String getFullName() {
return parent.getFullName() + "." + getName();
return parent.getFullName() + '.' + getName();
}
@Override
@@ -70,6 +70,6 @@ public class ResourceFile {
@Override
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
}
}
+8 -3
View File
@@ -11,7 +11,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DependencyCollector;
@@ -42,6 +41,7 @@ import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
@@ -62,7 +62,10 @@ public class Jadx {
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new DebugInfoParseVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
@@ -78,7 +81,9 @@ public class Jadx {
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
passes.add(new DebugInfoApplyVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
@@ -119,7 +119,7 @@ public class ClsSet {
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + '/' + CLST_FILENAME));
save(out);
} finally {
close(out);
@@ -151,7 +151,7 @@ public class AnnotationGen {
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable) val).iterator();
Iterator<?> it = ((Iterable<?>) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
@@ -164,7 +164,7 @@ public class AnnotationGen {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
}
}
@@ -143,7 +143,7 @@ public class ClassGen {
clsCode.attachDefinition(cls);
clsCode.add(cls.getShortName());
addGenericMap(clsCode, cls.getGenericMap());
addGenericMap(clsCode, cls.getGenericMap(), true);
clsCode.add(' ');
ArgType sup = cls.getSuperClass();
@@ -174,7 +174,7 @@ public class ClassGen {
}
}
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
if (gmap == null || gmap.isEmpty()) {
return false;
}
@@ -199,6 +199,10 @@ public class ClassGen {
code.add(g.getObject());
} else {
useClass(code, g);
if (classDeclaration && !cls.getAlias().isInner()) {
addImport(ClassInfo.extCls(cls.root(), g));
}
}
if (it.hasNext()) {
code.add(" & ");
@@ -532,7 +536,7 @@ public class ClassGen {
&& importCls.getShortName().equals(shortName)) {
if (extClsInfo.isInner()) {
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
return parent + "." + shortName;
return parent + '.' + shortName;
} else {
return fullName;
}
@@ -92,7 +92,7 @@ public class MethodGen {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (classGen.addGenericMap(code, mth.getGenericMap())) {
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
code.add(' ');
}
if (ai.isConstructor()) {
@@ -230,7 +230,7 @@ public class MethodGen {
}
if (addLabels && needLabel(insn, prevInsn)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.startLine(getLabelName(insn.getOffset()) + ':');
code.incIndent();
}
try {
@@ -126,7 +126,7 @@ public class NameGen {
String name = var.getName();
String varName = name != null ? name : guessName(var);
if (NameMapper.isReserved(varName)) {
varName = varName + "R";
varName = varName + 'R';
}
if (!NameMapper.isValidIdentifier(varName)) {
varName = getFallbackName(var);
@@ -153,7 +153,7 @@ public class TypeGen {
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
return Double.toString(d) + 'd';
}
public static String formatFloat(float f) {
@@ -175,6 +175,6 @@ public class TypeGen {
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
return Float.toString(f) + 'f';
}
}
@@ -17,6 +17,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
@@ -225,6 +226,9 @@ public class Deobfuscator {
clsInfo.rename(cls.dex().root(), fullName);
}
for (FieldNode field : cls.getFields()) {
if (field.contains(AFlag.DONT_RENAME)) {
continue;
}
renameField(field);
}
for (MethodNode mth : cls.getMethods()) {
@@ -391,7 +395,7 @@ public class Deobfuscator {
return null;
}
}
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
@@ -481,7 +485,7 @@ public class Deobfuscator {
private String prepareNamePart(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
return 'x' + Integer.toHexString(name.hashCode());
}
return NameMapper.removeInvalidCharsMiddle(name);
}
@@ -126,7 +126,7 @@ public class NameMapper {
* </ul><p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
if (isValidIdentifier(name)) {
return name;
}
int len = name.length();
@@ -15,6 +15,7 @@ public enum AFlag {
DONT_WRAP,
DONT_INLINE,
DONT_GENERATE, // process as usual, but don't output to generated code
DONT_RENAME, // do not rename during deobfuscation
REMOVE, // can be completely removed
ADDED_TO_REGION,
@@ -61,7 +61,4 @@ public class AType<T extends IAttribute> {
// registers
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
private AType() {
}
}
@@ -121,6 +121,6 @@ public class AttributeStorage {
if (list.isEmpty()) {
return "";
}
return "A[" + Utils.listToString(list) + "]";
return "A[" + Utils.listToString(list) + ']';
}
}
@@ -42,6 +42,6 @@ public class Annotation {
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
}
}
@@ -12,7 +12,7 @@ import jadx.core.utils.Utils;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
private final Map<String, Annotation> map;
@@ -43,6 +43,6 @@ public class EdgeInsnAttr implements IAttribute {
@Override
public String toString() {
return "EDGE_INSN: " + start + "->" + end + " " + insn;
return "EDGE_INSN: " + start + "->" + end + ' ' + insn;
}
}
@@ -44,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
@Override
public String toString() {
return "REPLACE: " + replaceType + " " + replaceObj;
return "REPLACE: " + replaceType + ' ' + replaceObj;
}
}
@@ -34,9 +34,9 @@ public class JadxError {
}
if (cause != null) {
str.append(cause.getClass());
str.append(":");
str.append(':');
str.append(cause.getMessage());
str.append("\n");
str.append('\n');
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -25,10 +25,10 @@ public class PhiListAttr implements IAttribute {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
}
for (PhiInsn phiInsn : list) {
sb.append("\n ").append(phiInsn).append(" ").append(phiInsn.getAttributesString());
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
}
return sb.toString();
}
@@ -53,6 +53,6 @@ public class RegDebugInfoAttr implements IAttribute {
@Override
public String toString() {
return "D('" + name + "' " + type + ")";
return "D('" + name + "' " + type + ')';
}
}
@@ -203,6 +203,6 @@ public class AccessInfo {
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
}
}
@@ -91,7 +91,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
String parClsName = pkg + '.' + clsName.substring(0, sep);
if (pkg.isEmpty()) {
parClsName = clsName.substring(0, sep);
}
@@ -110,7 +110,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
String innerSep = raw ? "$" : ".";
return parentClass.makeFullClsName(parentClass.getShortName(), raw) + innerSep + shortName;
}
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
return pkg.isEmpty() ? shortName : pkg + '.' + shortName;
}
public String makeRawFullName() {
@@ -148,7 +148,7 @@ public final class ClassInfo implements Comparable<ClassInfo> {
if (parentClass == null) {
return name;
}
return parentClass.getNameWithoutPackage() + "." + name;
return parentClass.getNameWithoutPackage() + '.' + name;
}
public ClassInfo getParentClass() {
@@ -54,11 +54,11 @@ public final class FieldInfo {
}
public String getFullId() {
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + name + ":" + TypeGen.signature(type);
return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type);
}
public boolean isRenamed() {
@@ -93,6 +93,6 @@ public final class FieldInfo {
@Override
public String toString() {
return declClass + "." + name + " " + type;
return declClass + "." + name + ' ' + type;
}
}
@@ -65,15 +65,15 @@ public final class MethodInfo {
}
public String getFullName() {
return declClass.getFullName() + "." + name;
return declClass.getFullName() + '.' + name;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
return declClass.getFullName() + '.' + shortId;
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + shortId;
return declClass.makeRawFullName() + '.' + shortId;
}
/**
@@ -151,7 +151,7 @@ public final class MethodInfo {
@Override
public String toString() {
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
return declClass.getFullName() + '.' + name
+ '(' + Utils.listToString(args) + "):" + retType;
}
}
@@ -76,8 +76,8 @@ public class ArithNode extends InsnNode {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ getArg(0) + ' '
+ op.getSymbol() + ' '
+ getArg(1);
}
}
@@ -35,6 +35,6 @@ public final class ConstClassNode extends InsnNode {
@Override
public String toString() {
return super.toString() + " " + clsType;
return super.toString() + ' ' + clsType;
}
}
@@ -34,6 +34,6 @@ public final class ConstStringNode extends InsnNode {
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
return super.toString() + " \"" + str + '"';
}
}
@@ -127,7 +127,7 @@ public class IfNode extends GotoNode {
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
+ getArg(0) + ' ' + op.getSymbol() + ' ' + getArg(1)
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
}
}
@@ -47,7 +47,7 @@ public class IndexInsnNode extends InsnNode {
+ Utils.listToString(getArguments());
default:
return super.toString() + " " + InsnUtils.indexToString(index);
return super.toString() + ' ' + InsnUtils.indexToString(index);
}
}
}
@@ -579,7 +579,7 @@ public class InsnDecoder {
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
default:
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + "'");
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + '\'');
}
}
@@ -73,7 +73,7 @@ public class InvokeNode extends InsnNode implements CallMthInterface {
+ InsnUtils.insnTypeToString(insnType)
+ (getResult() == null ? "" : getResult() + " = ")
+ Utils.listToString(getArguments())
+ " " + mth
+ ' ' + mth
+ " type: " + type;
}
}
@@ -255,7 +255,7 @@ public abstract class ArgType {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
return "? " + (bounds == -1 ? "super" : "extends") + ' ' + type;
}
}
@@ -271,7 +271,7 @@ public abstract class ArgType {
}
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
super(outerType.getObject() + "$" + innerName);
super(outerType.getObject() + '$' + innerName);
this.outerType = outerType;
this.generics = generics;
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
@@ -301,7 +301,7 @@ public abstract class ArgType {
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToStr(generics) + ">";
return super.toString() + '<' + Utils.arrayToStr(generics) + '>';
}
}
@@ -415,7 +415,7 @@ public abstract class ArgType {
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
} else {
return "?[" + Utils.arrayToStr(possibleTypes) + "]";
return "?[" + Utils.arrayToStr(possibleTypes) + ']';
}
}
}
@@ -94,6 +94,6 @@ public class CodeVar {
@Override
public String toString() {
return (isFinal ? "final " : "") + type + " " + name;
return (isFinal ? "final " : "") + type + ' ' + name;
}
}
@@ -81,6 +81,6 @@ public final class FieldArg extends RegisterArg {
@Override
public String toString() {
return "(" + field + ")";
return "(" + field + ')';
}
}
@@ -62,6 +62,6 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + type + "\n " + wrappedInsn + ")";
return "(wrap: " + type + "\n " + wrappedInsn + ')';
}
}
@@ -74,10 +74,10 @@ public final class LiteralArg extends InsnArg {
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + type + ")";
return '(' + value + ' ' + type + ')';
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + type + ")";
return "(" + literal + ' ' + type + ')';
}
}
}
@@ -44,6 +44,6 @@ public final class NamedArg extends InsnArg implements Named {
@Override
public String toString() {
return "(" + name + " " + type + ")";
return '(' + name + ' ' + type + ')';
}
}
@@ -160,14 +160,14 @@ public class SSAVar extends AttrNode {
}
public String toShortString() {
return "r" + regNum + "v" + version;
return "r" + regNum + 'v' + version;
}
@Override
public String toString() {
return toShortString()
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
+ " " + typeInfo.getType();
+ ' ' + typeInfo.getType();
}
public String getDetailedVarInfo(MethodNode mth) {
@@ -106,6 +106,6 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
@Override
public String toString() {
return super.toString() + " " + callMth + " " + callType;
return super.toString() + ' ' + callMth + ' ' + callType;
}
}
@@ -192,6 +192,6 @@ public class BlockNode extends AttrNode implements IBlock {
@Override
public String toString() {
return "B:" + id + ":" + InsnUtils.formatOffset(startOffset);
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
}
}
@@ -231,7 +231,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return;
}
if (fileName.contains("$")
&& fileName.endsWith("$" + name)) {
&& fileName.endsWith('$' + name)) {
return;
}
ClassInfo parentClass = clsInfo.getTopParentClass();
@@ -685,7 +685,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
@Override
public String toString() {
return parentClass + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
+ '(' + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
+ retType;
}
}
@@ -53,6 +53,6 @@ public final class Region extends AbstractRegion {
@Override
public String toString() {
return "R" + baseString();
return 'R' + baseString();
}
}
@@ -44,6 +44,6 @@ public final class Compare {
@Override
public String toString() {
return getA() + " " + getOp().getSymbol() + " " + getB();
return getA() + " " + getOp().getSymbol() + ' ' + getB();
}
}
@@ -226,7 +226,7 @@ public final class IfCondition {
case TERNARY:
return first() + " ? " + second() + " : " + third();
case NOT:
return "!(" + first() + ")";
return "!(" + first() + ')';
case AND:
case OR:
String op = mode == Mode.OR ? " || " : " && ";
@@ -30,6 +30,6 @@ public class ExcHandlerAttr implements IAttribute {
public String toString() {
return "ExcHandler: " + (handler.isFinally()
? " FINALLY"
: handler.catchTypeStr() + " " + handler.getArg());
: handler.catchTypeStr() + ' ' + handler.getArg());
}
}
@@ -96,8 +96,8 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.startLine("MethodNode[shape=record,label=\"{");
dot.add(escape(mth.getAccessFlags().makeString()));
dot.add(escape(mth.getReturnType() + " "
+ mth.getParentClass() + "." + mth.getName()
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
+ mth.getParentClass() + '.' + mth.getName()
+ '(' + Utils.listToString(mth.getArguments(true)) + ") "));
String attrs = attributesString(mth);
if (!attrs.isEmpty()) {
@@ -241,9 +241,9 @@ public class DotGraphVisitor extends AbstractVisitor {
if (c instanceof BlockNode) {
name = "Node_" + ((BlockNode) c).getId();
} else if (c instanceof IBlock) {
name = "Node_" + c.getClass().getSimpleName() + "_" + c.hashCode();
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
} else {
name = "cluster_" + c.getClass().getSimpleName() + "_" + c.hashCode();
name = "cluster_" + c.getClass().getSimpleName() + '_' + c.hashCode();
}
return name;
}
@@ -76,10 +76,11 @@ public class RenameVisitor extends AbstractVisitor {
String newShortName = fixClsShortName(clsName);
if (!newShortName.equals(clsName)) {
classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true));
alias = classInfo.getAlias();
}
if (alias.getPackage().isEmpty()) {
String fullName = alias.makeFullClsName(alias.getShortName(), true);
String newFullName = Consts.DEFAULT_PACKAGE_NAME + "." + fullName;
String newFullName = Consts.DEFAULT_PACKAGE_NAME + '.' + fullName;
classInfo.rename(cls.root(), newFullName);
}
}
@@ -92,7 +93,11 @@ public class RenameVisitor extends AbstractVisitor {
if (firstChar == '$') {
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
}
return NameMapper.removeInvalidChars(clsName, "C");
String cleanClsName = NameMapper.removeInvalidChars(clsName, "C");
if (!NameMapper.isValidIdentifier(cleanClsName)) {
return 'C' + cleanClsName;
}
return cleanClsName;
}
private void checkFields(ClassNode cls) {
@@ -65,8 +65,8 @@ public class InsnsSlice {
public String toString() {
return "{["
+ insnsList.stream().map(insn -> insn.getType().toString()).collect(Collectors.joining(", "))
+ "]"
+ ']'
+ (complete ? " complete" : "")
+ "}";
+ '}';
}
}
@@ -113,7 +113,7 @@ public final class LocalVar {
@Override
public String toString() {
return InsnUtils.formatOffset(startAddr)
+ "-" + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
+ '-' + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
+ ": r" + regNum + " '" + name + "' " + type;
}
}
@@ -140,7 +140,7 @@ public class IfRegionVisitor extends AbstractVisitor {
|| ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!hasBranchTerminator(ifRegion.getThenRegion())) {
if (!RegionUtils.hasExitBlock(ifRegion.getThenRegion())) {
return false;
}
// code style check:
@@ -162,12 +162,6 @@ public class IfRegionVisitor extends AbstractVisitor {
return false;
}
private static boolean hasBranchTerminator(IContainer region) {
// TODO: check for exception throw
return RegionUtils.hasExitBlock(region)
|| RegionUtils.hasBreakInsn(region);
}
private static void invertIfRegion(IfRegion ifRegion) {
IContainer elseRegion = ifRegion.getElseRegion();
if (elseRegion != null) {
@@ -33,9 +33,9 @@ import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
// all checks passed
initInsn.add(AFlag.DONT_GENERATE);
incrInsn.add(AFlag.DONT_GENERATE);
LoopType arrForEach = checkArrayForEach(mth, initInsn, incrInsn, condition);
LoopType arrForEach = checkArrayForEach(mth, loopRegion, initInsn, incrInsn, condition);
if (arrForEach != null) {
loopRegion.setType(arrForEach);
} else {
@@ -134,7 +134,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
return true;
}
private static LoopType checkArrayForEach(MethodNode mth, InsnNode initInsn, InsnNode incrInsn,
private static LoopType checkArrayForEach(MethodNode mth, LoopRegion loopRegion, InsnNode initInsn, InsnNode incrInsn,
IfCondition condition) {
if (!(incrInsn instanceof ArithNode)) {
return null;
@@ -195,6 +195,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
if (iterVar == null) {
return null;
}
if (!usedOnlyInLoop(mth, loopRegion, iterVar)) {
return null;
}
// array for each loop confirmed
incrInsn.getResult().add(AFlag.DONT_GENERATE);
@@ -7,6 +7,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
@@ -75,7 +76,7 @@ public class RegionMaker {
int startBlockId = startBlock.getId();
if (processedBlocks.get(startBlockId)) {
mth.addWarn("Removed duplicated region for block: " + startBlock + " " + startBlock.getAttributesString());
mth.addWarn("Removed duplicated region for block: " + startBlock + ' ' + startBlock.getAttributesString());
return r;
}
processedBlocks.set(startBlockId);
@@ -192,7 +193,7 @@ public class RegionMaker {
// add 'break' instruction before path cross between main loop exit and sub-exit
for (Edge exitEdge : loop.getExitEdges()) {
if (exitBlocks.contains(exitEdge.getSource())) {
insertBreak(stack, loopExit, exitEdge);
insertLoopBreak(stack, loop, loopExit, exitEdge);
}
}
}
@@ -290,6 +291,9 @@ public class RegionMaker {
}
}
}
if (found && !checkLoopExits(loop, block)) {
found = false;
}
if (found) {
return loopRegion;
}
@@ -298,6 +302,32 @@ public class RegionMaker {
return null;
}
private boolean checkLoopExits(LoopInfo loop, BlockNode mainExitBlock) {
List<Edge> exitEdges = loop.getExitEdges();
if (exitEdges.size() < 2) {
return true;
}
Optional<Edge> mainEdgeOpt = exitEdges.stream().filter(edge -> edge.getSource() == mainExitBlock).findFirst();
if (!mainEdgeOpt.isPresent()) {
throw new JadxRuntimeException("Not found exit edge by exit block: " + mainExitBlock);
}
Edge mainExitEdge = mainEdgeOpt.get();
BlockNode mainOutBlock = skipSyntheticSuccessor(mainExitEdge.getTarget());
for (Edge exitEdge : exitEdges) {
if (exitEdge != mainExitEdge) {
BlockNode outBlock = skipSyntheticSuccessor(exitEdge.getTarget());
// all exit paths must be same or don't cross (will be inside loop)
if (!isEqualPaths(mainOutBlock, outBlock)) {
BlockNode crossBlock = BlockUtils.getPathCross(mth, mainOutBlock, outBlock);
if (crossBlock != null) {
return false;
}
}
}
}
return true;
}
private BlockNode makeEndlessLoop(IRegion curRegion, RegionStack stack, LoopInfo loop, BlockNode loopStart) {
LoopRegion loopRegion = new LoopRegion(curRegion, loop, null, false);
curRegion.getSubBlocks().add(loopRegion);
@@ -312,7 +342,7 @@ public class RegionMaker {
if (exitEdges.size() == 1) {
Edge exitEdge = exitEdges.get(0);
BlockNode exit = exitEdge.getTarget();
if (insertBreak(stack, exit, exitEdge)) {
if (insertLoopBreak(stack, loop, exit, exitEdge)) {
BlockNode nextBlock = getNextBlock(exit);
if (nextBlock != null) {
stack.addExit(nextBlock);
@@ -326,10 +356,10 @@ public class RegionMaker {
for (BlockNode block : blocks) {
if (BlockUtils.isPathExists(exit, block)) {
stack.addExit(block);
insertBreak(stack, block, exitEdge);
insertLoopBreak(stack, loop, block, exitEdge);
out = block;
} else {
insertBreak(stack, exit, exitEdge);
insertLoopBreak(stack, loop, exit, exitEdge);
}
}
}
@@ -388,7 +418,7 @@ public class RegionMaker {
return true;
}
private boolean insertBreak(RegionStack stack, BlockNode loopExit, Edge exitEdge) {
private boolean insertLoopBreak(RegionStack stack, LoopInfo loop, BlockNode loopExit, Edge exitEdge) {
BlockNode exit = exitEdge.getTarget();
BlockNode insertBlock = null;
boolean confirm = false;
@@ -427,6 +457,7 @@ public class RegionMaker {
return false;
}
InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0);
breakInsn.addAttr(AType.LOOP, loop);
EdgeInsnAttr.addEdgeInsn(insertBlock, insertBlock.getSuccessors().get(0), breakInsn);
stack.addExit(exit);
// add label to 'break' if needed
@@ -102,6 +102,13 @@ public class RegionMakerVisitor extends AbstractVisitor {
if (!insnAttr.getStart().equals(last)) {
return;
}
if (last instanceof BlockNode) {
BlockNode block = (BlockNode) last;
if (block.getInstructions().isEmpty()) {
block.getInstructions().add(insnAttr.getInsn());
return;
}
}
List<InsnNode> insns = Collections.singletonList(insnAttr.getInsn());
region.add(new InsnContainer(insns));
}
@@ -28,6 +28,6 @@ class VarUsage {
@Override
public String toString() {
return "{" + (var == null ? "-" : var.toShortString()) + ", a:" + assigns + ", u:" + uses + "}";
return '{' + (var == null ? "-" : var.toShortString()) + ", a:" + assigns + ", u:" + uses + '}';
}
}
@@ -137,6 +137,6 @@ final class ArgsInfo {
public String toString() {
return "ArgsInfo: |" + inlineBorder
+ " ->" + (inlinedInsn == null ? "-" : inlinedInsn.pos)
+ " " + args + " : " + insn;
+ ' ' + args + " : " + insn;
}
}
@@ -45,6 +45,6 @@ public abstract class AbstractTypeConstraint implements ITypeConstraint {
@Override
public String toString() {
return "(" + insn.getType() + ":" + Utils.listToString(relatedVars, SSAVar::toShortString) + ")";
return "(" + insn.getType() + ':' + Utils.listToString(relatedVars, SSAVar::toShortString) + ')';
}
}
@@ -57,7 +57,7 @@ public class InsnUtils {
return "";
}
if (index instanceof String) {
return "\"" + index + "\"";
return "\"" + index + '"';
}
return index.toString();
}
@@ -44,6 +44,7 @@ public class InstructionRemover {
public void add(InsnNode insn) {
toRemove.add(insn);
}
public void addAndUnbind(MethodNode mth, InsnNode insn) {
toRemove.add(insn);
unbindInsn(mth, insn);
@@ -8,6 +8,9 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
@@ -91,22 +94,71 @@ public class RegionUtils {
}
/**
* Return true if last block in region has no successors
* Return true if last block in region has no successors or jump out insn (return or break)
*/
public static boolean hasExitBlock(IContainer container) {
return hasExitBlock(container, container);
}
private static boolean hasExitBlock(IContainer rootContainer, IContainer container) {
if (container instanceof BlockNode) {
return ((BlockNode) container).getSuccessors().isEmpty();
BlockNode blockNode = (BlockNode) container;
if (blockNode.getSuccessors().isEmpty()) {
return true;
}
return isInsnExitContainer(rootContainer, (IBlock) container);
} else if (container instanceof IBranchRegion) {
return false;
} else if (container instanceof IBlock) {
return true;
return isInsnExitContainer(rootContainer, (IBlock) container);
} else if (container instanceof IRegion) {
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
return !blocks.isEmpty()
&& hasExitBlock(blocks.get(blocks.size() - 1));
&& hasExitBlock(rootContainer, blocks.get(blocks.size() - 1));
} else {
throw new JadxRuntimeException(unknownContainerType(container));
}
}
private static boolean isInsnExitContainer(IContainer rootContainer, IBlock block) {
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn == null) {
return false;
}
InsnType insnType = lastInsn.getType();
if (insnType == InsnType.RETURN) {
return true;
}
if (insnType == InsnType.THROW) {
// check if after throw execution can continue in current container
CatchAttr catchAttr = lastInsn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
for (ExceptionHandler handler : catchAttr.getTryBlock().getHandlers()) {
if (RegionUtils.isRegionContainsBlock(rootContainer, handler.getHandlerBlock())) {
return false;
}
}
}
return true;
}
if (insnType == InsnType.BREAK) {
AttrList<LoopInfo> loopInfoAttrList = lastInsn.get(AType.LOOP);
if (loopInfoAttrList != null) {
for (LoopInfo loopInfo : loopInfoAttrList.getList()) {
if (!RegionUtils.isRegionContainsBlock(rootContainer, loopInfo.getStart())) {
return true;
}
}
}
LoopLabelAttr loopLabelAttr = lastInsn.get(AType.LOOP_LABEL);
if (loopLabelAttr != null
&& !RegionUtils.isRegionContainsBlock(rootContainer, loopLabelAttr.getLoop().getStart())) {
return true;
}
}
return false;
}
public static boolean hasBreakInsn(IContainer container) {
if (container instanceof IBlock) {
return BlockUtils.checkLastInsnType((IBlock) container, InsnType.BREAK);
@@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
@@ -124,6 +125,7 @@ public class AndroidResourcesUtils {
if (fieldNode != null
&& !fieldNode.getName().equals(resName)
&& NameMapper.isValidIdentifier(resName)) {
fieldNode.add(AFlag.DONT_RENAME);
fieldNode.getFieldInfo().setAlias(resName);
}
}
@@ -131,7 +133,7 @@ public class AndroidResourcesUtils {
@NotNull
private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) {
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + typeName,
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + '$' + typeName,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
resCls.addInnerClass(newTypeCls);
if (rClsExists) {
@@ -27,6 +27,6 @@ public class DexFile {
@Override
public String toString() {
return inputFile + (name.isEmpty() ? "" : ":" + name);
return inputFile + (name.isEmpty() ? "" : ':' + name);
}
}
@@ -77,7 +77,7 @@ public class FileUtils {
public static File createTempDir(String suffix) {
try {
Path path = Files.createTempDirectory("jadx-tmp-" + System.nanoTime() + "-" + suffix);
Path path = Files.createTempDirectory("jadx-tmp-" + System.nanoTime() + '-' + suffix);
path.toFile().deleteOnExit();
return path.toFile();
} catch (IOException e) {
@@ -470,7 +470,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
private boolean isDeobfCandidateAttr(String shortNsName, String attrName) {
String fullName;
if (shortNsName != null) {
fullName = shortNsName + ":" + attrName;
fullName = shortNsName + ':' + attrName;
} else {
return false;
}
@@ -43,7 +43,7 @@ public class ManifestAttributes {
@Override
public String toString() {
return "[" + type + ", " + values + "]";
return "[" + type + ", " + values + ']';
}
}
@@ -173,7 +173,7 @@ public class ManifestAttributes {
StringBuilder sb = new StringBuilder();
for (Map.Entry<Long, String> entry : attr.getValues().entrySet()) {
if (value == entry.getKey()) {
sb = new StringBuilder(entry.getValue() + "|");
sb = new StringBuilder(entry.getValue() + '|');
break;
} else if ((value & entry.getKey()) == entry.getKey()) {
sb.append(entry.getValue()).append('|');
@@ -138,6 +138,9 @@ public class ParserConstants {
protected static final int FLAG_COMPLEX = 0x0001;
// If set, this resource has been declared public, so libraries are allowed to reference it.
protected static final int FLAG_PUBLIC = 0x0002;
// If set, this is a weak resource and may be overriden by strong resources of the same name/type.
// This is only useful during linking with other resource tables.
protected static final int FLAG_WEAK = 0x0004;
/**
* ResTable_map
@@ -1,5 +1,6 @@
package jadx.core.xmlgen;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
@@ -143,6 +144,25 @@ public class ParserStream {
input.reset();
}
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public void readFully(byte[] b, int off, int len) throws IOException {
readPos += len;
if (len < 0) {
throw new IndexOutOfBoundsException();
}
int n = 0;
while (n < len) {
int count = input.read(b, off + n, len - n);
if (count < 0) {
throw new EOFException();
}
n += count;
}
}
@Override
public String toString() {
return "pos: 0x" + Long.toHexString(readPos);
@@ -96,6 +96,6 @@ public class ResContainer implements Comparable<ResContainer> {
@Override
public String toString() {
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + "}";
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + '}';
}
}
@@ -21,6 +21,8 @@ public class ResTableParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);
private static final int KNOWN_CONFIG_BYTES = 56;
private static final class PackageChunk {
private final int id;
private final String name;
@@ -79,7 +81,7 @@ public class ResTableParser extends CommonBinaryParser {
Set<String> addedValues = new HashSet<>();
for (ResourceEntry ri : resStorage.getResources()) {
if (addedValues.add(ri.getTypeName() + "." + ri.getKeyName())) {
if (addedValues.add(ri.getTypeName() + '.' + ri.getKeyName())) {
String format = String.format("<public type=\"%s\" name=\"%s\" id=\"%s\" />",
ri.getTypeName(), ri.getKeyName(), ri.getId());
writer.startLine(format);
@@ -194,6 +196,11 @@ public class ResTableParser extends CommonBinaryParser {
EntryConfig config = parseConfig();
if (config.isInvalid) {
String typeName = pkg.getTypeStrings()[id - 1];
LOG.warn("Invalid config flags detected: " + typeName + config.getQualifiers());
}
int[] entryIndexes = new int[entryCount];
for (int i = 0; i < entryCount; i++) {
entryIndexes[i] = is.readInt32();
@@ -208,8 +215,7 @@ public class ResTableParser extends CommonBinaryParser {
}
private void parseEntry(PackageChunk pkg, int typeId, int entryId, EntryConfig config) throws IOException {
/* int size = */
is.readInt16();
int size = is.readInt16();
int flags = is.readInt16();
int key = is.readInt32();
@@ -219,17 +225,17 @@ public class ResTableParser extends CommonBinaryParser {
ResourceEntry ri = new ResourceEntry(resRef, pkg.getName(), typeName, keyName);
ri.setConfig(config);
if ((flags & FLAG_COMPLEX) == 0) {
ri.setSimpleValue(parseValue());
} else {
if ((flags & FLAG_COMPLEX) != 0 || size == 16) {
int parentRef = is.readInt32();
ri.setParentRef(parentRef);
int count = is.readInt32();
ri.setParentRef(parentRef);
List<RawNamedValue> values = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
values.add(parseValueMap());
}
ri.setNamedValues(values);
} else {
ri.setSimpleValue(parseValue());
}
resStorage.add(ri);
}
@@ -250,132 +256,99 @@ public class ResTableParser extends CommonBinaryParser {
private EntryConfig parseConfig() throws IOException {
long start = is.getPos();
int size = is.readInt32();
if (size < 28) {
throw new IOException("Config size < 28");
}
EntryConfig config = new EntryConfig();
boolean isInvalid = false;
is.readInt16(); //mcc
is.readInt16(); //mnc
short mcc = (short) is.readInt16();
short mnc = (short) is.readInt16();
config.setLanguage(parseLocale());
config.setCountry(parseLocale());
char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a');
char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0');
int orientation = is.readInt8();
int touchscreen = is.readInt8();
byte orientation = (byte) is.readInt8();
byte touchscreen = (byte) is.readInt8();
int density = is.readInt16();
if (density != 0) {
config.setDensity(parseDensity(density));
}
is.readInt8(); // keyboard
is.readInt8(); // navigation
is.readInt8(); // inputFlags
byte keyboard = (byte) is.readInt8();
byte navigation = (byte) is.readInt8();
byte inputFlags = (byte) is.readInt8();
is.readInt8(); // inputPad0
int screenWidth = is.readInt16();
int screenHeight = is.readInt16();
short screenWidth = (short) is.readInt16();
short screenHeight = (short) is.readInt16();
if (screenWidth != 0 && screenHeight != 0) {
config.setScreenSize(screenWidth + "x" + screenHeight);
short sdkVersion = (short) is.readInt16();
is.readInt16(); // minorVersion must always be 0
byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = (byte) is.readInt8();
uiMode = (byte) is.readInt8();
smallestScreenWidthDp = (short) is.readInt16();
}
int sdkVersion = is.readInt16();
if (sdkVersion != 0) {
config.setSdkVersion("v" + sdkVersion);
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = (short) is.readInt16();
screenHeightDp = (short) is.readInt16();
}
int minorVersion = is.readInt16();
int screenLayout = is.readInt8();
int uiMode = is.readInt8();
int smallestScreenWidthDp = is.readInt16();
int screenWidthDp = is.readInt16();
int screenHeightDp = is.readInt16();
if (screenLayout != 0) {
config.setScreenLayout(parseScreenLayout(screenLayout));
char[] localeScript = null;
char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
}
if (smallestScreenWidthDp != 0) {
config.setSmallestScreenWidthDp("sw" + smallestScreenWidthDp + "dp");
byte screenLayout2 = 0;
byte colorMode = 0;
if (size >= 52) {
screenLayout2 = (byte) is.readInt8();
colorMode = (byte) is.readInt8();
is.readInt16(); // reserved padding
}
if (orientation != 0) {
config.setOrientation(parseOrientation(orientation));
}
is.skipToPos(start + size, "Config skip trailing bytes");
if (screenWidthDp != 0) {
config.setScreenWidthDp("w" + screenWidthDp + "dp");
}
if (screenHeightDp != 0) {
config.setScreenHeightDp("h" + screenHeightDp + "dp");
}
is.skipToPos(start + size, "Skip config parsing");
return config;
return new EntryConfig(mcc, mnc, language, country,
orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, localeScript, localeVariant, screenLayout2,
colorMode, isInvalid, size);
}
private String parseOrientation(int orientation) {
if (orientation == 1) {
return "port";
} else if (orientation == 2) {
return "land";
} else {
return "o" + orientation;
private char[] unpackLocaleOrRegion(byte in0, byte in1, char base) {
// check high bit, if so we have a packed 3 letter code
if (((in0 >> 7) & 1) == 1) {
int first = in1 & 0x1F;
int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3);
int third = (in0 & 0x7C) >> 2;
// since this function handles languages & regions, we add the value(s) to the base char
// which is usually 'a' or '0' depending on language or region.
return new char[]{(char) (first + base), (char) (second + base), (char) (third + base)};
}
return new char[]{(char) in0, (char) in1};
}
private String parseScreenLayout(int screenLayout) {
switch (screenLayout) {
case 1:
return "small";
case 2:
return "normal";
case 3:
return "large";
case 4:
return "xlarge";
case 64:
return "ldltr";
case 128:
return "ldrtl";
default:
return "sl" + screenLayout;
}
}
private String parseDensity(int density) {
if (density == 120) {
return "ldpi";
} else if (density == 160) {
return "mdpi";
} else if (density == 240) {
return "hdpi";
} else if (density == 320) {
return "xhdpi";
} else if (density == 480) {
return "xxhdpi";
} else if (density == 640) {
return "xxxhdpi";
} else {
return density + "dpi";
}
}
private String parseLocale() throws IOException {
int b1 = is.readInt8();
int b2 = is.readInt8();
String str = null;
if (b1 != 0 && b2 != 0) {
if ((b1 & 0x80) == 0) {
str = new String(new char[]{(char) b1, (char) b2});
} else {
LOG.warn("TODO: parse locale: 0x{}{}", Integer.toHexString(b1), Integer.toHexString(b2));
private String readScriptOrVariantChar(int length) throws IOException {
long start = is.getPos();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < length; i++) {
short ch = (short) is.readInt8();
if (ch == 0) {
break;
}
sb.append((char) ch);
}
return str;
is.skipToPos(start + length, "readScriptOrVariantChar");
return sb.toString();
}
}
@@ -15,6 +15,8 @@ import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
import static jadx.core.xmlgen.ParserConstants.PLURALS_MAP;
public class ResXmlGen {
private static final Set<String> SKIP_RES_TYPES = new HashSet<>(Arrays.asList(
@@ -147,16 +149,24 @@ public class ResXmlGen {
}
}
}
if (typeName.equals("attr")) {
if (nameStr != null) {
addSimpleValue(cw, typeName, itemTag, nameStr, valueStr, "");
}
} else if (typeName.equals("style")) {
if (nameStr != null) {
addSimpleValue(cw, typeName, itemTag, nameStr, "", valueStr);
}
} else {
addSimpleValue(cw, typeName, itemTag, null, null, valueStr);
switch (typeName) {
case "attr":
if (nameStr != null) {
addSimpleValue(cw, typeName, itemTag, nameStr, valueStr, "");
}
break;
case "style":
if (nameStr != null) {
addSimpleValue(cw, typeName, itemTag, nameStr, "", valueStr);
}
break;
case "plurals":
final String quantity = PLURALS_MAP.get(value.getNameRef());
addSimpleValue(cw, typeName, itemTag, "quantity", quantity, valueStr);
break;
default:
addSimpleValue(cw, typeName, itemTag, null, null, valueStr);
break;
}
}
@@ -194,10 +204,10 @@ public class ResXmlGen {
private String getFileName(ResourceEntry ri) {
StringBuilder sb = new StringBuilder();
String locale = ri.getConfig().getLocale();
String qualifiers = ri.getConfig().getQualifiers();
sb.append("res/values");
if (!locale.isEmpty()) {
sb.append('-').append(locale);
if (!qualifiers.isEmpty()) {
sb.append(qualifiers);
}
sb.append('/');
sb.append(ri.getTypeName());
@@ -47,7 +47,7 @@ public class ResourceStorage {
public Map<Integer, String> getResourcesNames() {
Map<Integer, String> map = new HashMap<>();
for (ResourceEntry entry : list) {
map.put(entry.getId(), entry.getTypeName() + "/" + entry.getKeyName());
map.put(entry.getId(), entry.getTypeName() + '/' + entry.getKeyName());
}
return map;
}
@@ -1,159 +1,649 @@
/**
* Copyright (C) 2018 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2018 Connor Tumbleson <connor.tumbleson@gmail.com>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jadx.core.xmlgen.entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Original source code can be found
* <a href="https://raw.githubusercontent.com/iBotPeaches/Apktool/master/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java">here</a>
*/
public class EntryConfig {
private String language;
private String country;
private String density;
private String screenSize;
private String sdkVersion;
private String screenLayout;
private String smallestScreenWidthDp;
private String orientation;
private String screenWidthDp;
private String screenHeightDp;
public final short mcc;
public final short mnc;
public String getLanguage() {
return language;
public final char[] language;
public final char[] region;
public final byte orientation;
public final byte touchscreen;
public final int density;
public final byte keyboard;
public final byte navigation;
public final byte inputFlags;
public final short screenWidth;
public final short screenHeight;
public final short sdkVersion;
public final byte screenLayout;
public final byte uiMode;
public final short smallestScreenWidthDp;
public final short screenWidthDp;
public final short screenHeightDp;
private final char[] localeScript;
private final char[] localeVariant;
private final byte screenLayout2;
private final byte colorMode;
public final boolean isInvalid;
private final String mQualifiers;
private final int size;
public EntryConfig() {
mcc = 0;
mnc = 0;
language = new char[]{'\00', '\00'};
region = new char[]{'\00', '\00'};
orientation = ORIENTATION_ANY;
touchscreen = TOUCHSCREEN_ANY;
density = DENSITY_DEFAULT;
keyboard = KEYBOARD_ANY;
navigation = NAVIGATION_ANY;
inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY;
screenWidth = 0;
screenHeight = 0;
sdkVersion = 0;
screenLayout = SCREENLONG_ANY | SCREENSIZE_ANY;
uiMode = UI_MODE_TYPE_ANY | UI_MODE_NIGHT_ANY;
smallestScreenWidthDp = 0;
screenWidthDp = 0;
screenHeightDp = 0;
localeScript = null;
localeVariant = null;
screenLayout2 = 0;
colorMode = COLOR_WIDE_UNDEFINED;
isInvalid = false;
mQualifiers = "";
size = 0;
}
public void setLanguage(String language) {
public EntryConfig(short mcc, short mnc, char[] language,
char[] region, byte orientation,
byte touchscreen, int density, byte keyboard, byte navigation,
byte inputFlags, short screenWidth, short screenHeight,
short sdkVersion, byte screenLayout, byte uiMode,
short smallestScreenWidthDp, short screenWidthDp,
short screenHeightDp, char[] localeScript, char[] localeVariant,
byte screenLayout2, byte colorMode, boolean isInvalid, int size) {
if (orientation < 0 || orientation > 3) {
LOG.warn("Invalid orientation value: " + orientation);
orientation = 0;
isInvalid = true;
}
if (touchscreen < 0 || touchscreen > 3) {
LOG.warn("Invalid touchscreen value: " + touchscreen);
touchscreen = 0;
isInvalid = true;
}
if (density < -1) {
LOG.warn("Invalid density value: " + density);
density = 0;
isInvalid = true;
}
if (keyboard < 0 || keyboard > 3) {
LOG.warn("Invalid keyboard value: " + keyboard);
keyboard = 0;
isInvalid = true;
}
if (navigation < 0 || navigation > 4) {
LOG.warn("Invalid navigation value: " + navigation);
navigation = 0;
isInvalid = true;
}
if (localeScript != null && localeScript.length != 0) {
if (localeScript[0] == '\00') {
localeScript = null;
}
} else {
localeScript = null;
}
if (localeVariant != null && localeVariant.length != 0) {
if (localeVariant[0] == '\00') {
localeVariant = null;
}
} else {
localeVariant = null;
}
this.mcc = mcc;
this.mnc = mnc;
this.language = language;
this.region = region;
this.orientation = orientation;
this.touchscreen = touchscreen;
this.density = density;
this.keyboard = keyboard;
this.navigation = navigation;
this.inputFlags = inputFlags;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
this.sdkVersion = sdkVersion;
this.screenLayout = screenLayout;
this.uiMode = uiMode;
this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
this.localeScript = localeScript;
this.localeVariant = localeVariant;
this.screenLayout2 = screenLayout2;
this.colorMode = colorMode;
this.isInvalid = isInvalid;
this.size = size;
mQualifiers = generateQualifiers();
}
public String getCountry() {
return country;
public String getQualifiers() {
return mQualifiers;
}
public void setCountry(String country) {
this.country = country;
private String generateQualifiers() {
StringBuilder ret = new StringBuilder();
if (mcc != 0) {
ret.append("-mcc").append(String.format("%03d", mcc));
if (mnc != MNC_ZERO) {
if (mnc != 0) {
ret.append("-mnc");
if (size <= 32) {
if (mnc > 0 && mnc < 10) {
ret.append(String.format("%02d", mnc));
} else {
ret.append(String.format("%03d", mnc));
}
} else {
ret.append(mnc);
}
}
} else {
ret.append("-mnc00");
}
} else {
if (mnc != 0) {
ret.append("-mnc").append(mnc);
}
}
ret.append(getLocaleString());
switch (screenLayout & MASK_LAYOUTDIR) {
case SCREENLAYOUT_LAYOUTDIR_RTL:
ret.append("-ldrtl");
break;
case SCREENLAYOUT_LAYOUTDIR_LTR:
ret.append("-ldltr");
break;
}
if (smallestScreenWidthDp != 0) {
ret.append("-sw").append(smallestScreenWidthDp).append("dp");
}
if (screenWidthDp != 0) {
ret.append("-w").append(screenWidthDp).append("dp");
}
if (screenHeightDp != 0) {
ret.append("-h").append(screenHeightDp).append("dp");
}
switch (screenLayout & MASK_SCREENSIZE) {
case SCREENSIZE_SMALL:
ret.append("-small");
break;
case SCREENSIZE_NORMAL:
ret.append("-normal");
break;
case SCREENSIZE_LARGE:
ret.append("-large");
break;
case SCREENSIZE_XLARGE:
ret.append("-xlarge");
break;
}
switch (screenLayout & MASK_SCREENLONG) {
case SCREENLONG_YES:
ret.append("-long");
break;
case SCREENLONG_NO:
ret.append("-notlong");
break;
}
switch (screenLayout2 & MASK_SCREENROUND) {
case SCREENLAYOUT_ROUND_NO:
ret.append("-notround");
break;
case SCREENLAYOUT_ROUND_YES:
ret.append("-round");
break;
}
switch (colorMode & COLOR_HDR_MASK) {
case COLOR_HDR_YES:
ret.append("-highdr");
break;
case COLOR_HDR_NO:
ret.append("-lowdr");
break;
}
switch (colorMode & COLOR_WIDE_MASK) {
case COLOR_WIDE_YES:
ret.append("-widecg");
break;
case COLOR_WIDE_NO:
ret.append("-nowidecg");
break;
}
switch (orientation) {
case ORIENTATION_PORT:
ret.append("-port");
break;
case ORIENTATION_LAND:
ret.append("-land");
break;
case ORIENTATION_SQUARE:
ret.append("-square");
break;
}
switch (uiMode & MASK_UI_MODE_TYPE) {
case UI_MODE_TYPE_CAR:
ret.append("-car");
break;
case UI_MODE_TYPE_DESK:
ret.append("-desk");
break;
case UI_MODE_TYPE_TELEVISION:
ret.append("-television");
break;
case UI_MODE_TYPE_SMALLUI:
ret.append("-smallui");
break;
case UI_MODE_TYPE_MEDIUMUI:
ret.append("-mediumui");
break;
case UI_MODE_TYPE_LARGEUI:
ret.append("-largeui");
break;
case UI_MODE_TYPE_GODZILLAUI:
ret.append("-godzillaui");
break;
case UI_MODE_TYPE_HUGEUI:
ret.append("-hugeui");
break;
case UI_MODE_TYPE_APPLIANCE:
ret.append("-appliance");
break;
case UI_MODE_TYPE_WATCH:
ret.append("-watch");
break;
case UI_MODE_TYPE_VR_HEADSET:
ret.append("-vrheadset");
break;
}
switch (uiMode & MASK_UI_MODE_NIGHT) {
case UI_MODE_NIGHT_YES:
ret.append("-night");
break;
case UI_MODE_NIGHT_NO:
ret.append("-notnight");
break;
}
switch (density) {
case DENSITY_DEFAULT:
break;
case DENSITY_LOW:
ret.append("-ldpi");
break;
case DENSITY_MEDIUM:
ret.append("-mdpi");
break;
case DENSITY_HIGH:
ret.append("-hdpi");
break;
case DENSITY_TV:
ret.append("-tvdpi");
break;
case DENSITY_XHIGH:
ret.append("-xhdpi");
break;
case DENSITY_XXHIGH:
ret.append("-xxhdpi");
break;
case DENSITY_XXXHIGH:
ret.append("-xxxhdpi");
break;
case DENSITY_ANY:
ret.append("-anydpi");
break;
case DENSITY_NONE:
ret.append("-nodpi");
break;
default:
ret.append('-').append(density).append("dpi");
}
switch (touchscreen) {
case TOUCHSCREEN_NOTOUCH:
ret.append("-notouch");
break;
case TOUCHSCREEN_STYLUS:
ret.append("-stylus");
break;
case TOUCHSCREEN_FINGER:
ret.append("-finger");
break;
}
switch (inputFlags & MASK_KEYSHIDDEN) {
case KEYSHIDDEN_NO:
ret.append("-keysexposed");
break;
case KEYSHIDDEN_YES:
ret.append("-keyshidden");
break;
case KEYSHIDDEN_SOFT:
ret.append("-keyssoft");
break;
}
switch (keyboard) {
case KEYBOARD_NOKEYS:
ret.append("-nokeys");
break;
case KEYBOARD_QWERTY:
ret.append("-qwerty");
break;
case KEYBOARD_12KEY:
ret.append("-12key");
break;
}
switch (inputFlags & MASK_NAVHIDDEN) {
case NAVHIDDEN_NO:
ret.append("-navexposed");
break;
case NAVHIDDEN_YES:
ret.append("-navhidden");
break;
}
switch (navigation) {
case NAVIGATION_NONAV:
ret.append("-nonav");
break;
case NAVIGATION_DPAD:
ret.append("-dpad");
break;
case NAVIGATION_TRACKBALL:
ret.append("-trackball");
break;
case NAVIGATION_WHEEL:
ret.append("-wheel");
break;
}
if (screenWidth != 0 && screenHeight != 0) {
if (screenWidth > screenHeight) {
ret.append(String.format("-%dx%d", screenWidth, screenHeight));
} else {
ret.append(String.format("-%dx%d", screenHeight, screenWidth));
}
}
if (sdkVersion > 0 && sdkVersion >= getNaturalSdkVersionRequirement()) {
ret.append("-v").append(sdkVersion);
}
if (isInvalid) {
ret.append("-ERR").append(sErrCounter++);
}
return ret.toString();
}
public String getLocale() {
private short getNaturalSdkVersionRequirement() {
if ((uiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET || (colorMode & COLOR_WIDE_MASK) != 0 || ((colorMode & COLOR_HDR_MASK) != 0)) {
return SDK_OREO;
}
if ((screenLayout2 & MASK_SCREENROUND) != 0) {
return SDK_MNC;
}
if (density == DENSITY_ANY) {
return SDK_LOLLIPOP;
}
if (smallestScreenWidthDp != 0 || screenWidthDp != 0 || screenHeightDp != 0) {
return SDK_HONEYCOMB_MR2;
}
if ((uiMode & (MASK_UI_MODE_TYPE | MASK_UI_MODE_NIGHT)) != UI_MODE_NIGHT_ANY) {
return SDK_FROYO;
}
if ((screenLayout & (MASK_SCREENSIZE | MASK_SCREENLONG)) != SCREENSIZE_ANY || density != DENSITY_DEFAULT) {
return SDK_DONUT;
}
return 0;
}
private String getLocaleString() {
StringBuilder sb = new StringBuilder();
if (screenSize != null) {
if (sb.length() != 0) {
sb.append("-");
// check for old style non BCP47 tags
// allows values-xx-rXX, values-xx, values-xxx-rXX
// denies values-xxx, anything else
if (localeVariant == null && localeScript == null && (region[0] != '\00' || language[0] != '\00') &&
region.length != 3) {
sb.append('-').append(language);
if (region[0] != '\00') {
sb.append("-r").append(region);
}
sb.append(screenSize);
} else if (screenHeightDp != null) {
if (sb.length() != 0) {
sb.append("-");
} else { // BCP47
if (language[0] == '\00' && region[0] == '\00') {
return sb.toString(); // early return, no language or region
}
sb.append(screenHeightDp);
} else if (screenWidthDp != null) {
if (sb.length() != 0) {
sb.append("-");
sb.append("-b+");
if (language[0] != '\00') {
sb.append(language);
}
sb.append(screenWidthDp);
} else if (screenLayout != null) {
if (sb.length() != 0) {
sb.append("-");
if (localeScript != null && localeScript.length == 4) {
sb.append('+').append(localeScript);
}
sb.append(screenLayout);
} else if (smallestScreenWidthDp != null) {
if (sb.length() != 0) {
sb.append("-");
if ((region.length == 2 || region.length == 3) && region[0] != '\00') {
sb.append('+').append(region);
}
sb.append(smallestScreenWidthDp);
} else if (density != null) {
sb.append(density);
}
if (language != null) {
if (sb.length() != 0) {
sb.append("-");
if (localeVariant != null && localeVariant.length >= 5) {
sb.append('+').append(toUpper(localeVariant));
}
sb.append(language);
}
if (country != null) {
sb.append("-r").append(country);
}
if (orientation != null) {
if (sb.length() != 0) {
sb.append("-");
}
sb.append(orientation);
}
if (sdkVersion != null) {
if (sb.length() != 0) {
sb.append("-");
}
sb.append(sdkVersion);
}
return sb.toString();
}
public String getDensity() {
return density;
}
public void setDensity(String density) {
this.density = density;
private String toUpper(char[] character) {
StringBuilder sb = new StringBuilder();
for (char ch : character) {
sb.append(Character.toUpperCase(ch));
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getLocale());
if (sb.length() != 0) {
sb.insert(0, " [");
sb.append(']');
return !getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]";
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
return sb.toString();
if (getClass() != obj.getClass()) {
return false;
}
final EntryConfig other = (EntryConfig) obj;
return this.mQualifiers.equals(other.mQualifiers);
}
public void setScreenSize(String screenSize) {
this.screenSize = screenSize;
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + this.mQualifiers.hashCode();
return hash;
}
public String getScreenSize() {
return screenSize;
}
// TODO: Dirty static hack. This counter should be a part of ResPackage,
// but it would be hard right now and this feature is very rarely used.
private static int sErrCounter = 0;
public void setSdkVersion(String sdkVersion) {
this.sdkVersion = sdkVersion;
}
public final static byte SDK_BASE = 1;
public final static byte SDK_BASE_1_1 = 2;
public final static byte SDK_CUPCAKE = 3;
public final static byte SDK_DONUT = 4;
public final static byte SDK_ECLAIR = 5;
public final static byte SDK_ECLAIR_0_1 = 6;
public final static byte SDK_ECLAIR_MR1 = 7;
public final static byte SDK_FROYO = 8;
public final static byte SDK_GINGERBREAD = 9;
public final static byte SDK_GINGERBREAD_MR1 = 10;
public final static byte SDK_HONEYCOMB = 11;
public final static byte SDK_HONEYCOMB_MR1 = 12;
public final static byte SDK_HONEYCOMB_MR2 = 13;
public final static byte SDK_ICE_CREAM_SANDWICH = 14;
public final static byte SDK_ICE_CREAM_SANDWICH_MR1 = 15;
public final static byte SDK_JELLY_BEAN = 16;
public final static byte SDK_JELLY_BEAN_MR1 = 17;
public final static byte SDK_JELLY_BEAN_MR2 = 18;
public final static byte SDK_KITKAT = 19;
public final static byte SDK_LOLLIPOP = 21;
public final static byte SDK_LOLLIPOP_MR1 = 22;
public final static byte SDK_MNC = 23;
public final static byte SDK_NOUGAT = 24;
public final static byte SDK_NOUGAT_MR1 = 25;
public final static byte SDK_OREO = 26;
public final static byte SDK_OREO_MR1 = 27;
public final static byte SDK_P = 28;
public String getSdkVersion() {
return sdkVersion;
}
public final static byte ORIENTATION_ANY = 0;
public final static byte ORIENTATION_PORT = 1;
public final static byte ORIENTATION_LAND = 2;
public final static byte ORIENTATION_SQUARE = 3;
public void setScreenLayout(String screenLayout) {
this.screenLayout = screenLayout;
}
public final static byte TOUCHSCREEN_ANY = 0;
public final static byte TOUCHSCREEN_NOTOUCH = 1;
public final static byte TOUCHSCREEN_STYLUS = 2;
public final static byte TOUCHSCREEN_FINGER = 3;
public String getScreenLayout() {
return screenLayout;
}
public final static int DENSITY_DEFAULT = 0;
public final static int DENSITY_LOW = 120;
public final static int DENSITY_MEDIUM = 160;
public final static int DENSITY_400 = 190;
public final static int DENSITY_TV = 213;
public final static int DENSITY_HIGH = 240;
public final static int DENSITY_XHIGH = 320;
public final static int DENSITY_XXHIGH = 480;
public final static int DENSITY_XXXHIGH = 640;
public final static int DENSITY_ANY = 0xFFFE;
public final static int DENSITY_NONE = 0xFFFF;
public void setSmallestScreenWidthDp(String smallestScreenWidthDp) {
this.smallestScreenWidthDp = smallestScreenWidthDp;
}
public final static int MNC_ZERO = -1;
public String getSmallestScreenWidthDp() {
return smallestScreenWidthDp;
}
public final static short MASK_LAYOUTDIR = 0xc0;
public final static short SCREENLAYOUT_LAYOUTDIR_ANY = 0x00;
public final static short SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
public final static short SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
public final static short SCREENLAYOUT_LAYOUTDIR_SHIFT = 0x06;
public void setOrientation(String orientation) {
this.orientation = orientation;
}
public final static short MASK_SCREENROUND = 0x03;
public final static short SCREENLAYOUT_ROUND_ANY = 0;
public final static short SCREENLAYOUT_ROUND_NO = 0x1;
public final static short SCREENLAYOUT_ROUND_YES = 0x2;
public String getOrientation() {
return orientation;
}
public final static byte KEYBOARD_ANY = 0;
public final static byte KEYBOARD_NOKEYS = 1;
public final static byte KEYBOARD_QWERTY = 2;
public final static byte KEYBOARD_12KEY = 3;
public void setScreenWidthDp(String screenWidthDp) {
this.screenWidthDp = screenWidthDp;
}
public final static byte NAVIGATION_ANY = 0;
public final static byte NAVIGATION_NONAV = 1;
public final static byte NAVIGATION_DPAD = 2;
public final static byte NAVIGATION_TRACKBALL = 3;
public final static byte NAVIGATION_WHEEL = 4;
public String getScreenWidthDp() {
return screenWidthDp;
}
public final static byte MASK_KEYSHIDDEN = 0x3;
public final static byte KEYSHIDDEN_ANY = 0x0;
public final static byte KEYSHIDDEN_NO = 0x1;
public final static byte KEYSHIDDEN_YES = 0x2;
public final static byte KEYSHIDDEN_SOFT = 0x3;
public void setScreenHeightDp(String screenHeightDp) {
this.screenHeightDp = screenHeightDp;
}
public final static byte MASK_NAVHIDDEN = 0xc;
public final static byte NAVHIDDEN_ANY = 0x0;
public final static byte NAVHIDDEN_NO = 0x4;
public final static byte NAVHIDDEN_YES = 0x8;
public String getScreenHeightDp() {
return screenHeightDp;
}
public final static byte MASK_SCREENSIZE = 0x0f;
public final static byte SCREENSIZE_ANY = 0x00;
public final static byte SCREENSIZE_SMALL = 0x01;
public final static byte SCREENSIZE_NORMAL = 0x02;
public final static byte SCREENSIZE_LARGE = 0x03;
public final static byte SCREENSIZE_XLARGE = 0x04;
public final static byte MASK_SCREENLONG = 0x30;
public final static byte SCREENLONG_ANY = 0x00;
public final static byte SCREENLONG_NO = 0x10;
public final static byte SCREENLONG_YES = 0x20;
public final static byte MASK_UI_MODE_TYPE = 0x0f;
public final static byte UI_MODE_TYPE_ANY = 0x00;
public final static byte UI_MODE_TYPE_NORMAL = 0x01;
public final static byte UI_MODE_TYPE_DESK = 0x02;
public final static byte UI_MODE_TYPE_CAR = 0x03;
public final static byte UI_MODE_TYPE_TELEVISION = 0x04;
public final static byte UI_MODE_TYPE_APPLIANCE = 0x05;
public final static byte UI_MODE_TYPE_WATCH = 0x06;
public final static byte UI_MODE_TYPE_VR_HEADSET = 0x07;
// start - miui
public final static byte UI_MODE_TYPE_GODZILLAUI = 0x0b;
public final static byte UI_MODE_TYPE_SMALLUI = 0x0c;
public final static byte UI_MODE_TYPE_MEDIUMUI = 0x0d;
public final static byte UI_MODE_TYPE_LARGEUI = 0x0e;
public final static byte UI_MODE_TYPE_HUGEUI = 0x0f;
// end - miui
public final static byte MASK_UI_MODE_NIGHT = 0x30;
public final static byte UI_MODE_NIGHT_ANY = 0x00;
public final static byte UI_MODE_NIGHT_NO = 0x10;
public final static byte UI_MODE_NIGHT_YES = 0x20;
public final static byte COLOR_HDR_MASK = 0xC;
public final static byte COLOR_HDR_NO = 0x4;
public final static byte COLOR_HDR_SHIFT = 0x2;
public final static byte COLOR_HDR_UNDEFINED = 0x0;
public final static byte COLOR_HDR_YES = 0x8;
public final static byte COLOR_UNDEFINED = 0x0;
public final static byte COLOR_WIDE_UNDEFINED = 0x0;
public final static byte COLOR_WIDE_NO = 0x1;
public final static byte COLOR_WIDE_YES = 0x2;
public final static byte COLOR_WIDE_MASK = 0x3;
private static final Logger LOG = LoggerFactory.getLogger(EntryConfig.class);
}
@@ -75,6 +75,6 @@ public final class ResourceEntry {
@Override
public String toString() {
return " 0x" + Integer.toHexString(id) + " (" + id + ")" + config + " = " + typeName + "." + keyName;
return " 0x" + Integer.toHexString(id) + " (" + id + ')' + config + " = " + typeName + '.' + keyName;
}
}
@@ -60,7 +60,7 @@ public class ValuesParser extends ParserConstants {
if (nameStr == null) {
strList.add(valueStr);
} else {
strList.add(nameStr + "=" + valueStr);
strList.add(nameStr + '=' + valueStr);
}
}
return strList.toString();
@@ -110,7 +110,7 @@ public class ValuesParser extends ParserConstants {
}
return "?unknown_ref: " + Integer.toHexString(data);
}
return "@" + ri;
return '@' + ri;
}
case TYPE_ATTRIBUTE: {
@@ -122,7 +122,7 @@ public class ValuesParser extends ParserConstants {
}
return "?unknown_attr_ref: " + Integer.toHexString(data);
}
return "?" + ri;
return '?' + ri;
}
case TYPE_DIMENSION:
@@ -132,7 +132,7 @@ public class ValuesParser extends ParserConstants {
default:
LOG.warn("Unknown data type: 0x{} {}", Integer.toHexString(dataType), data);
return " ?0x" + Integer.toHexString(dataType) + " " + data;
return " ?0x" + Integer.toHexString(dataType) + ' ' + data;
}
}
@@ -215,7 +215,7 @@ public class ValuesParser extends ParserConstants {
}
private static String floatToString(float value) {
return doubleToString((double) value);
return doubleToString(value);
}
public static Map<Integer, String> getAndroidResMap() {
@@ -1,82 +0,0 @@
package jadx.tests
import jadx.core.dex.attributes.AType
import jadx.core.dex.attributes.AttributeStorage
import jadx.core.dex.attributes.IAttribute
import spock.lang.Specification
import static jadx.core.dex.attributes.AFlag.SYNTHETIC
class TestAttributeStorage extends Specification {
AttributeStorage storage
def setup() {
storage = new AttributeStorage()
}
def "add flag"() {
when:
storage.add(SYNTHETIC)
then:
storage.contains(SYNTHETIC)
}
def "remove flag"() {
setup:
storage.add(SYNTHETIC)
when:
storage.remove(SYNTHETIC)
then:
!storage.contains(SYNTHETIC)
}
def TEST = new AType<TestAttr>()
class TestAttr implements IAttribute {
AType<TestAttr> getType() { TEST }
}
def "add attribute"() {
setup:
def attr = new TestAttr()
when:
storage.add(attr)
then:
storage.contains(TEST)
storage.get(TEST) == attr
}
def "remove attribute"() {
setup:
def attr = new TestAttr()
storage.add(attr)
when:
storage.remove(attr)
then:
!storage.contains(TEST)
storage.get(TEST) == null
}
def "remove attribute other"() {
setup:
def attr = new TestAttr()
storage.add(attr)
when:
storage.remove(new TestAttr())
then:
storage.contains(TEST)
storage.get(TEST) == attr
}
def "clear"() {
setup:
storage.add(SYNTHETIC)
storage.add(new TestAttr())
when:
storage.clear()
then:
!storage.contains(SYNTHETIC)
!storage.contains(TEST)
}
}
@@ -1,39 +0,0 @@
package jadx.tests
import spock.lang.Specification
import static jadx.core.deobf.NameMapper.isValidFullIdentifier
class TestNameMapper extends Specification {
def "test is Valid Full Identifier"() {
expect:
isValidFullIdentifier(valid)
where:
valid << [
'C',
'Cc',
'b.C',
'b.Cc',
'aAa.b.Cc',
'a.b.Cc',
'a.b.C_c',
'a.b.C$c',
'a.b.C9'
]
}
def "test is not Valid Full Identifier"() {
expect:
!isValidFullIdentifier(invalid)
where:
invalid << [
'',
'5',
'7A',
'.C',
'b.9C',
'b..C',
]
}
}
@@ -1,107 +0,0 @@
package jadx.tests
import jadx.core.dex.instructions.args.ArgType
import jadx.core.dex.nodes.parser.SignatureParser
import spock.lang.Specification
import static jadx.core.dex.instructions.args.ArgType.*
class TestSignatureParser extends Specification {
def "simple types"() {
expect:
new SignatureParser(str).consumeType() == result
where:
str | result
"" | null
"I" | INT
"[I" | array(INT)
"Ljava/lang/Object;" | OBJECT
"[Ljava/lang/Object;" | array(OBJECT)
"[[I" | array(array(INT))
}
def "generics"() {
expect:
new SignatureParser(str).consumeType() == result
where:
str | result
"TD;" | genericType("D")
"La<TV;Lb;>;" | generic("La;", genericType("V"), object("b"))
"La<Lb<Lc;>;>;" | generic("La;", generic("Lb;", object("Lc;")))
"La/b/C<Ld/E<Lf/G;>;>;" | generic("La/b/C;", generic("Ld/E;", object("Lf/G;")))
"La<TD;>.c;" | genericInner(generic("La;", genericType("D")), "c", null)
"La<TD;>.c/d;" | genericInner(generic("La;", genericType("D")), "c.d", null)
"La<Lb;>.c<TV;>;" | genericInner(generic("La;", object("Lb;")), "c", genericType("V"))
}
def "inner generic"() {
expect:
new SignatureParser(str).consumeType().getObject() == result
where:
str | result
"La<TV;>.LinkedHashIterator<Lb\$c<Ls;TV;>;>;" | "a\$LinkedHashIterator"
}
def "wildcards"() {
expect:
new SignatureParser("La<$s>;").consumeType() == generic("La;", r as ArgType[])
where:
s | r
"*" | wildcard()
"+Lb;" | wildcard(object("b"), 1)
"-Lb;" | wildcard(object("b"), -1)
"+TV;" | wildcard(genericType("V"), 1)
"-TV;" | wildcard(genericType("V"), -1)
"**" | [wildcard(), wildcard()]
"*Lb;" | [wildcard(), object("b")]
"*TV;" | [wildcard(), genericType("V")]
"TV;*" | [genericType("V"), wildcard()]
"Lb;*" | [object("b"), wildcard()]
"***" | [wildcard(), wildcard(), wildcard()]
"*Lb;*" | [wildcard(), object("b"), wildcard()]
}
def "generic map"() {
expect:
new SignatureParser(str).consumeGenericMap() == result.collectEntries { [genericType(it.key), it.value] }
where:
str | result
"" | [:]
"<T:Ljava/lang/Object;>" | ["T": []]
"<K:Ljava/lang/Object;LongType:Ljava/lang/Object;>" | ["K": [], "LongType": []]
"<ResultT:Ljava/lang/Exception;:Ljava/lang/Object;>" | ["ResultT": [object("java.lang.Exception")]]
}
def "method args"() {
when:
def argTypes = new SignatureParser("(Ljava/util/List<*>;)V").consumeMethodArgs()
then:
argTypes.size() == 1
argTypes.get(0) == generic("Ljava/util/List;", wildcard())
}
def "method args 2"() {
when:
def argTypes = new SignatureParser("(La/b/C<TT;>.d/E;)V").consumeMethodArgs()
then:
argTypes.size() == 1
def argType = argTypes.get(0)
argType.getObject().indexOf('/') == -1
argTypes.get(0) == genericInner(generic("La/b/C;", genericType("T")), "d.E", null)
}
def "generic map: bad signature"() {
when:
def map = new SignatureParser("<A:Ljava/lang/Object;B").consumeGenericMap()
then:
notThrown(NullPointerException)
map.isEmpty()
}
}
@@ -1,43 +0,0 @@
package jadx.tests
import jadx.api.JadxArgs
import jadx.core.utils.StringUtils
import spock.lang.Specification
class TestStringUtils extends Specification {
def "unescape string"() {
def args = new JadxArgs()
args.setEscapeUnicode(true)
def stringUtils = new StringUtils(args)
expect:
stringUtils.unescapeString(input) == "\"$expected\""
where:
input | expected
"" | ""
"'" | "'"
"a" | "a"
"\n" | "\\n"
"\t" | "\\t"
"\r" | "\\r"
"\b" | "\\b"
"\f" | "\\f"
"\\" | "\\\\"
"\"" | "\\\""
"\u1234" | "\\u1234"
}
def "unescape char"() {
expect:
new StringUtils(new JadxArgs()).unescapeChar(input as char) == "'$expected'"
where:
input | expected
'a' | "a"
' ' | " "
'\n' | "\\n"
'\'' | "\\\'"
'\0' | "\\u0000"
}
}
@@ -0,0 +1,25 @@
package jadx;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates a test which is known to fail.
*
* <p>This would cause a failure to be considered as success and a success as failure,
* with the benefit of updating the related issue when it has been resolved even unintentionally.</p>
*
* <p>To have an effect, the test class must be annotated with:
*
* <code>
* &#064;ExtendWith(NotYetImplementedExtension.class)
* </code>
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface NotYetImplemented {
String value() default "";
}
@@ -0,0 +1,38 @@
package jadx;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
public class NotYetImplementedExtension implements AfterTestExecutionCallback, TestExecutionExceptionHandler {
private Set<Method> knownFailedMethods = new HashSet<>();
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
if (!isNotYetImplemented(context)) {
throw throwable;
}
knownFailedMethods.add(context.getTestMethod().get());
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
if (!knownFailedMethods.contains(context.getTestMethod().get())
&& isNotYetImplemented(context)
&& !context.getExecutionException().isPresent()) {
throw new AssertionError("Test "
+ context.getTestClass().get().getName() + '.' + context.getTestMethod().get().getName()
+ " is marked as @NotYetImplemented, but passes!");
}
}
private static boolean isNotYetImplemented(ExtensionContext context) {
return context.getTestMethod().get().getAnnotation(NotYetImplemented.class) != null
|| context.getTestClass().get().getAnnotation(NotYetImplemented.class) != null;
}
}
@@ -1,14 +1,14 @@
package jadx.api;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.files.FileUtils;
import static jadx.core.utils.files.FileUtils.toFile;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class JadxArgsValidatorOutDirsTest {
@@ -2,13 +2,13 @@ package jadx.api;
import java.io.File;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class JadxDecompilerTest {
@Test
@Ignore
@Disabled
public void testExampleUsage() {
JadxArgs args = new JadxArgs();
args.getInputFiles().add(new File("test.apk"));
@@ -1,12 +1,12 @@
package jadx.core.deobf;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import static jadx.core.deobf.NameMapper.isValidIdentifier;
import static jadx.core.deobf.NameMapper.removeInvalidChars;
import static jadx.core.deobf.NameMapper.removeInvalidCharsMiddle;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class NameMapperTest {
@@ -1,13 +1,13 @@
package jadx.core.dex.info;
import com.android.dx.rop.code.AccessFlags;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import jadx.core.dex.info.AccessInfo.AFType;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertSame;
public class AccessInfoTest {
@@ -2,9 +2,12 @@ package jadx.core.dex.visitors.typeinference;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import jadx.NotYetImplemented;
import jadx.NotYetImplementedExtension;
import jadx.api.JadxArgs;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.RootNode;
@@ -19,13 +22,14 @@ import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_ARRAY;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
import static jadx.core.dex.instructions.args.ArgType.array;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@ExtendWith(NotYetImplementedExtension.class)
public class TypeCompareTest {
private TypeCompare compare;
@Before
@BeforeEach
public void init() {
JadxArgs args = new JadxArgs();
RootNode root = new RootNode(args);
@@ -99,10 +103,15 @@ public class TypeCompareTest {
check(tType, ArgType.OBJECT, TypeCompareEnum.NARROW_BY_GENERIC);
check(ArgType.OBJECT, tType, TypeCompareEnum.WIDER_BY_GENERIC);
}
@Test
@NotYetImplemented
public void compareGenericTypesNYI() {
ArgType vType = ArgType.genericType("V");
// TODO: use extend types from generic declaration for more strict checks
// check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
// check(ArgType.STRING, vType, TypeCompareEnum.CONFLICT);
check(vType, ArgType.STRING, TypeCompareEnum.CONFLICT);
check(ArgType.STRING, vType, TypeCompareEnum.CONFLICT);
}
private void firstIsNarrow(ArgType first, ArgType second) {
@@ -35,16 +35,16 @@ import jadx.tests.api.compiler.StaticCompiler;
import jadx.tests.api.utils.TestUtils;
import static jadx.core.utils.files.FileUtils.addFileToJar;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public abstract class IntegrationTest extends TestUtils {
@@ -92,9 +92,21 @@ public abstract class IntegrationTest extends TestUtils {
}
public ClassNode getClassNodeFromFile(File file, String clsName) {
JadxDecompiler d = loadFiles(Collections.singletonList(file));
RootNode root = JadxInternalAccess.getRoot(d);
ClassNode cls = root.searchClassByName(clsName);
assertThat("Class not found: " + clsName, cls, notNullValue());
assertThat(clsName, is(cls.getClassInfo().getFullName()));
decompileAndCheckCls(d, cls);
return cls;
}
protected JadxDecompiler loadFiles(List<File> inputFiles) {
JadxDecompiler d = null;
try {
args.setInputFiles(Collections.singletonList(file));
args.setInputFiles(inputFiles);
d = new JadxDecompiler(args);
d.load();
} catch (Exception e) {
@@ -103,11 +115,10 @@ public abstract class IntegrationTest extends TestUtils {
}
RootNode root = JadxInternalAccess.getRoot(d);
insertResources(root);
return d;
}
ClassNode cls = root.searchClassByName(clsName);
assertThat("Class not found: " + clsName, cls, notNullValue());
assertThat(clsName, is(cls.getClassInfo().getFullName()));
protected void decompileAndCheckCls(JadxDecompiler d, ClassNode cls) {
if (unloadCls) {
decompile(d, cls);
} else {
@@ -120,8 +131,7 @@ public abstract class IntegrationTest extends TestUtils {
checkCode(cls);
compile(cls);
runAutoCheck(clsName);
return cls;
runAutoCheck(cls.getClassInfo().getFullName());
}
private void insertResources(RootNode root) {
@@ -162,9 +172,9 @@ public abstract class IntegrationTest extends TestUtils {
}
protected static void checkCode(ClassNode cls) {
assertFalse("Inconsistent cls: " + cls, hasErrors(cls));
assertFalse(hasErrors(cls), "Inconsistent cls: " + cls);
for (MethodNode mthNode : cls.getMethods()) {
assertFalse("Method with problems: " + mthNode, hasErrors(mthNode));
assertFalse(hasErrors(mthNode), "Method with problems: " + mthNode);
}
assertThat(cls.getCode().toString(), not(containsString("inconsistent")));
}
@@ -257,7 +267,7 @@ public abstract class IntegrationTest extends TestUtils {
try {
dynamicCompiler = new DynamicCompiler(cls);
boolean result = dynamicCompiler.compile();
assertTrue("Compilation failed", result);
assertTrue(result, "Compilation failed");
System.out.println("Compilation: PASSED");
} catch (Exception e) {
e.printStackTrace();
@@ -275,7 +285,7 @@ public abstract class IntegrationTest extends TestUtils {
}
public Method getReflectMethod(String method, Class<?>... types) {
assertNotNull("dynamicCompiler not ready", dynamicCompiler);
assertNotNull(dynamicCompiler, "dynamicCompiler not ready");
try {
return dynamicCompiler.getMethod(method, types);
} catch (Exception e) {
@@ -286,8 +296,8 @@ public abstract class IntegrationTest extends TestUtils {
}
public Object invoke(Method mth, Object... args) throws Exception {
assertNotNull("dynamicCompiler not ready", dynamicCompiler);
assertNotNull("unknown method", mth);
assertNotNull(dynamicCompiler, "dynamicCompiler not ready");
assertNotNull(mth, "unknown method");
return dynamicCompiler.invoke(mth, args);
}
@@ -427,7 +437,7 @@ public abstract class IntegrationTest extends TestUtils {
protected void setOutputCFG() {
this.args.setCfgOutput(true);
this.args.setRawCFGOutput(true);
} // Use only for debug purpose
} // Use only for debug purpose
@Deprecated
protected void setOutputRawCFG() {
@@ -6,11 +6,16 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.jf.smali.Smali;
import org.jf.smali.SmaliOptions;
import jadx.api.JadxDecompiler;
import jadx.core.dex.nodes.ClassNode;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
public abstract class SmaliTest extends IntegrationTest {
private static final String SMALI_TESTS_PROJECT = "jadx-core";
@@ -24,6 +29,10 @@ public abstract class SmaliTest extends IntegrationTest {
return getClassNodeFromFile(outDex, clsName);
}
protected ClassNode getClassNodeFromSmali(String clsName) {
return getClassNodeFromSmali(clsName, clsName);
}
protected ClassNode getClassNodeFromSmaliWithPath(String path, String clsName) {
return getClassNodeFromSmali(path + File.separatorChar + clsName, clsName);
}
@@ -32,17 +41,37 @@ public abstract class SmaliTest extends IntegrationTest {
return getClassNodeFromSmali(pkg + File.separatorChar + clsName, pkg + '.' + clsName);
}
protected ClassNode getClassNodeFromSmaliFiles(String pkg, String testName, String clsName, String... smaliFileNames) {
protected ClassNode getClassNodeFromSmaliFiles(String pkg, String testName, String clsName) {
File outDex = createTempFile(".dex");
List<File> smaliFiles = Arrays.stream(smaliFileNames)
.map(file -> getSmaliFile(pkg + File.separatorChar + testName + File.separatorChar + file))
.collect(Collectors.toList());
compileSmali(outDex, smaliFiles);
compileSmali(outDex, collectSmaliFiles(pkg, testName));
return getClassNodeFromFile(outDex, pkg + "." + clsName);
}
protected ClassNode getClassNodeFromSmali(String clsName) {
return getClassNodeFromSmali(clsName, clsName);
protected JadxDecompiler loadSmaliFile(String pkg, String smaliFileName) {
File outDex = createTempFile(".dex");
compileSmali(outDex, Collections.singletonList(getSmaliFile(pkg + File.separatorChar + smaliFileName)));
return loadFiles(Collections.singletonList(outDex));
}
protected JadxDecompiler loadSmaliFiles(String pkg, String testNameDir) {
File outDex = createTempFile(".dex");
compileSmali(outDex, collectSmaliFiles(pkg, testNameDir));
return loadFiles(Collections.singletonList(outDex));
}
private List<File> collectSmaliFiles(String pkg, @Nullable String testDir) {
String smaliFilesDir;
if (testDir == null) {
smaliFilesDir = pkg + File.separatorChar;
} else {
smaliFilesDir = pkg + File.separatorChar + testDir + File.separatorChar;
}
File smaliDir = new File(SMALI_TESTS_DIR, smaliFilesDir);
String[] smaliFileNames = smaliDir.list((dir, name) -> name.endsWith(".smali"));
assertThat("Smali files not found", smaliFileNames, notNullValue());
return Arrays.stream(smaliFileNames)
.map(file -> new File(smaliDir, file))
.collect(Collectors.toList());
}
private static File getSmaliFile(String baseName) {
@@ -22,7 +22,7 @@ public class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFile
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className,
Kind kind, FileObject sibling) throws IOException {
Kind kind, FileObject sibling) throws IOException {
JavaClassObject clsObject = new JavaClassObject(className, kind);
classLoader.getClsMap().put(className, clsObject);
return clsObject;
@@ -55,7 +55,7 @@ public class StaticCompiler {
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
FileObject sibling) throws IOException {
FileObject sibling) throws IOException {
if (kind == JavaFileObject.Kind.CLASS) {
File file = new File(outDir, className.replace('.', '/') + ".class");
files.add(file);
@@ -1,7 +1,11 @@
package jadx.tests.api.utils;
import org.junit.jupiter.api.extension.ExtendWith;
import jadx.NotYetImplementedExtension;
import jadx.core.codegen.CodeWriter;
@ExtendWith(NotYetImplementedExtension.class)
public class TestUtils {
public static String indent() {
@@ -24,9 +24,9 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.tests.api.IntegrationTest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public abstract class BaseExternalTest extends IntegrationTest {
private static final Logger LOG = LoggerFactory.getLogger(BaseExternalTest.class);
@@ -0,0 +1,84 @@
package jadx.tests.functional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttributeStorage;
import jadx.core.dex.attributes.IAttribute;
import static jadx.core.dex.attributes.AFlag.SYNTHETIC;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
public class AttributeStorageTest {
private AttributeStorage storage;
@BeforeEach
public void setup() {
storage = new AttributeStorage();
}
@Test
public void testAdd() {
storage.add(SYNTHETIC);
assertThat(storage.contains(SYNTHETIC), is(true));
}
@Test
public void testRemove() {
storage.add(SYNTHETIC);
storage.remove(SYNTHETIC);
assertThat(storage.contains(SYNTHETIC), is(false));
}
public static final AType<TestAttr> TEST = new AType<>();
public static class TestAttr implements IAttribute {
@Override
public AType<TestAttr> getType() {
return TEST;
}
}
@Test
public void testAddAttribute() {
TestAttr attr = new TestAttr();
storage.add(attr);
assertThat(storage.contains(TEST), is(true));
assertThat(storage.get(TEST), is(attr));
}
@Test
public void testRemoveAttribute() {
TestAttr attr = new TestAttr();
storage.add(attr);
storage.remove(attr);
assertThat(storage.contains(TEST), is(false));
assertThat(storage.get(TEST), nullValue());
}
@Test
public void testRemoveOtherAttribute() {
TestAttr attr = new TestAttr();
storage.add(attr);
storage.remove(new TestAttr());
assertThat(storage.contains(TEST), is(true));
assertThat(storage.get(TEST), is(attr));
}
@Test
public void clear() {
storage.add(SYNTHETIC);
storage.add(new TestAttr());
storage.clear();
assertThat(storage.contains(SYNTHETIC), is(false));
assertThat(storage.contains(TEST), is(false));
assertThat(storage.get(TEST), nullValue());
}
}
@@ -2,8 +2,8 @@ package jadx.tests.functional;
import java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.instructions.args.ArgType;
@@ -13,8 +13,8 @@ import jadx.core.utils.exceptions.DecodeException;
import static jadx.core.dex.instructions.args.ArgType.STRING;
import static jadx.core.dex.instructions.args.ArgType.object;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,7 +26,7 @@ public class JadxClasspathTest {
private DexNode dex;
private ClspGraph clsp;
@Before
@BeforeEach
public void initClsp() throws IOException, DecodeException {
clsp = new ClspGraph();
clsp.load();

Some files were not shown because too many files have changed in this diff Show More