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:
+23
-16
@@ -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
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>
|
||||
* @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
Reference in New Issue
Block a user