diff --git a/.travis.yml b/.travis.yml index 6e13ba0f8..b702ae7b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,7 @@ before_install: - chmod +x gradlew # override install to skip 'gradle assemble' -install: - - true +install: true env: global: @@ -21,34 +20,21 @@ env: - JADX_LAST_TAG=$(git describe --abbrev=0 --tags) - JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)" -matrix: +jdk: + - openjdk8 + - oraclejdk8 + - openjdk11 + +script: ./gradlew clean build + +jobs: include: - - env: JDK=oracle-8 - jdk: oraclejdk8 - - env: JDK=openjdk11 - jdk: openjdk11 + - stage: deploy-unstable + jdk: openjdk8 + if: branch = master AND repo = env(MAIN_REPO) AND type = push + script: bash scripts/travis-master.sh -script: - - java -version - - ./gradlew clean build - -deploy: - - provider: script - skip_cleanup: true - on: - branch: master - tags: false - condition: $JDK = oracle-8 - script: bash scripts/travis-master.sh - - - provider: script - skip_cleanup: true - on: - branch: release - tags: false - condition: $JDK = oracle-8 - script: bash scripts/travis-release.sh - -notifications: - email: - - skylot@gmail.com + - stage: deploy-release + jdk: openjdk8 + if: branch = release AND repo = env(MAIN_REPO) AND type = push + script: bash scripts/travis-release.sh diff --git a/build.gradle b/build.gradle index fee10c7d3..e27970da9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'org.sonarqube' version '2.7' - id 'com.github.ben-manes.versions' version '0.20.0' + id 'com.github.ben-manes.versions' version '0.21.0' } ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" @@ -35,12 +35,12 @@ allprojects { } dependencies { - compile 'org.slf4j:slf4j-api:1.7.25' + 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.23.4' + testCompile 'org.mockito:mockito-core:2.25.0' testCompile 'org.spockframework:spock-core:1.1-groovy-2.4' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 558870dad..44e7c4d1d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index edb6beb52..037d9b9cb 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -5,13 +5,17 @@ dependencies { compile files('lib/dx-1.16.jar') compile 'commons-io:commons-io:2.6' - compile 'org.ow2.asm:asm:7.0' - compile 'org.jetbrains:annotations:16.0.3' - compile 'uk.com.robust-it:cloning:1.9.11' + compile 'org.ow2.asm:asm:7.1' + compile 'org.jetbrains:annotations:17.0.0' + compile 'uk.com.robust-it:cloning:1.9.12' - testCompile 'org.smali:smali:2.2.5' - testCompile 'org.smali:baksmali:2.2.5' + testCompile 'org.smali:smali:2.2.6' + testCompile 'org.smali:baksmali:2.2.6' testCompile 'org.apache.commons:commons-lang3:3.8.1' + + // update dependency in smali + testCompile 'com.google.guava:guava:27.1-jre' + testCompile 'com.beust:jcommander:1.74' } diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 1b92e8a72..66881b1af 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import jadx.core.Jadx; import jadx.core.ProcessClass; -import jadx.core.codegen.CodeGen; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; @@ -59,7 +58,6 @@ public final class JadxDecompiler { private RootNode root; private List passes; - private CodeGen codeGen; private List classes; private List resources; @@ -97,7 +95,6 @@ public final class JadxDecompiler { void init() { this.passes = Jadx.getPassesList(args); - this.codeGen = new CodeGen(); } void reset() { @@ -106,7 +103,6 @@ public final class JadxDecompiler { xmlParser = null; root = null; passes = null; - codeGen = null; } public static String getVersion() { @@ -215,9 +211,11 @@ public final class JadxDecompiler { List clsList = new ArrayList<>(classNodeList.size()); classesMap.clear(); for (ClassNode classNode : classNodeList) { - JavaClass javaClass = new JavaClass(classNode, this); - clsList.add(javaClass); - classesMap.put(classNode, javaClass); + if (!classNode.contains(AFlag.DONT_GENERATE)) { + JavaClass javaClass = new JavaClass(classNode, this); + clsList.add(javaClass); + classesMap.put(classNode, javaClass); + } } classes = Collections.unmodifiableList(clsList); } @@ -289,7 +287,7 @@ public final class JadxDecompiler { } void processClass(ClassNode cls) { - ProcessClass.process(cls, passes, codeGen); + ProcessClass.process(cls, passes, true); } RootNode getRoot() { diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 985510e9b..d35067806 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -2,8 +2,6 @@ package jadx.core; import java.util.List; -import org.jetbrains.annotations.Nullable; - import jadx.core.codegen.CodeGen; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.visitors.DepthTraversal; @@ -19,8 +17,8 @@ public final class ProcessClass { private ProcessClass() { } - public static void process(ClassNode cls, List passes, @Nullable CodeGen codeGen) { - if (codeGen == null && cls.getState() == PROCESSED) { + public static void process(ClassNode cls, List passes, boolean generateCode) { + if (!generateCode && cls.getState() == PROCESSED) { return; } synchronized (getSyncObj(cls)) { @@ -33,9 +31,9 @@ public final class ProcessClass { } cls.setState(PROCESSED); } - if (cls.getState() == PROCESSED && codeGen != null) { + if (cls.getState() == PROCESSED && generateCode) { processDependencies(cls, passes); - codeGen.visit(cls); + CodeGen.generate(cls); } } catch (Exception e) { ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e); @@ -48,6 +46,6 @@ public final class ProcessClass { } private static void processDependencies(ClassNode cls, List passes) { - cls.getDependencies().forEach(depCls -> process(depCls, passes, null)); + cls.getDependencies().forEach(depCls -> process(depCls, passes, false)); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 26b428a94..14732d245 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -97,7 +97,7 @@ public class ClassGen { imports.clear(); } clsCode.add(clsBody); - return clsCode; + return clsCode.finish(); } public void addClassCode(CodeWriter code) throws CodegenException { diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java index eae73cb09..926443efc 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeGen.java @@ -1,15 +1,20 @@ package jadx.core.codegen; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.nodes.ClassNode; import jadx.core.utils.exceptions.CodegenException; public class CodeGen { - public boolean visit(ClassNode cls) throws CodegenException { - ClassGen clsGen = new ClassGen(cls, cls.root().getArgs()); - CodeWriter clsCode = clsGen.makeClass(); - clsCode.finish(); - cls.setCode(clsCode); - return false; + public static void generate(ClassNode cls) throws CodegenException { + if (cls.contains(AFlag.DONT_GENERATE)) { + cls.setCode(CodeWriter.EMPTY); + } else { + ClassGen clsGen = new ClassGen(cls, cls.root().getArgs()); + cls.setCode(clsGen.makeClass()); + } + } + + private CodeGen() { } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 31a2e2783..4f5adc847 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -24,6 +24,8 @@ public class CodeWriter { public static final String NL = System.getProperty("line.separator"); public static final String INDENT_STR = " "; + public static final CodeWriter EMPTY = new CodeWriter().finish(); + private static final boolean ADD_LINE_NUMBERS = false; private static final String[] INDENT_CACHE = { @@ -250,7 +252,7 @@ public class CodeWriter { return lineMap; } - public void finish() { + public CodeWriter finish() { removeFirstEmptyLine(); buf.trimToSize(); code = buf.toString(); @@ -266,11 +268,12 @@ public class CodeWriter { it.remove(); } } + return this; } private void removeFirstEmptyLine() { int len = NL.length(); - if (buf.substring(0, len).equals(NL)) { + if (buf.length() > len && buf.substring(0, len).equals(NL)) { buf.delete(0, len); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 788fafae9..6df3dedd4 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -48,7 +48,6 @@ import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.utils.ErrorsCounter; import jadx.core.utils.RegionUtils; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -549,7 +548,7 @@ public class InsnGen { throws CodegenException { ClassNode cls = mth.dex().resolveClass(insn.getClassType()); if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) { - inlineAnonymousConstr(code, cls, insn); + inlineAnonymousConstructor(code, cls, insn); return; } if (insn.isSelf()) { @@ -567,20 +566,14 @@ public class InsnGen { generateMethodArguments(code, insn, 0, callMth); } - private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { - // anonymous class construction - if (cls.contains(AFlag.DONT_GENERATE)) { - code.add("/* anonymous class already generated */"); - ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls); - return; - } + private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { + cls.add(AFlag.DONT_GENERATE); ArgType parent; if (cls.getInterfaces().size() == 1) { parent = cls.getInterfaces().get(0); } else { parent = cls.getSuperClass(); } - cls.add(AFlag.DONT_GENERATE); MethodNode defCtr = cls.getDefaultConstructor(); if (defCtr != null) { if (RegionUtils.notEmpty(defCtr.getRegion())) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 73b028650..3ae9e8de3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import jadx.core.Consts; import jadx.core.codegen.CodeWriter; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.annotations.Annotation; import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.SourceFileAttr; @@ -123,7 +124,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { accFlagsValue = cls.getAccessFlags(); } this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); - + markAnonymousClass(this); buildCache(); } catch (Exception e) { throw new JadxRuntimeException("Error decode class: " + clsInfo, e); @@ -394,6 +395,29 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { && getDefaultConstructor() != null; } + public boolean isLambdaCls() { + return accessFlags.isSynthetic() && accessFlags.isFinal() + && clsInfo.getType().getObject().contains(".-$$Lambda$") + && countStaticFields() == 0; + } + + private int countStaticFields() { + int c = 0; + for (FieldNode field : fields) { + if (field.getAccessFlags().isStatic()) { + c++; + } + } + return c; + } + + private static void markAnonymousClass(ClassNode cls) { + if (cls.isAnonymous() || cls.isLambdaCls()) { + cls.add(AFlag.ANONYMOUS_CLASS); + cls.add(AFlag.DONT_GENERATE); + } + } + @Nullable public MethodNode getClassInitMth() { return searchMethodByName("()V"); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java index 0fd0061e8..15a9b1c2e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/InsnNode.java @@ -327,4 +327,24 @@ public class InsnNode extends LineAttrNode { } return INSN_CLONER.deepClone(this); } + + public boolean canThrowException() { + switch (getType()) { + case RETURN: + case IF: + case GOTO: + case MOVE: + case MOVE_EXCEPTION: + case NEG: + case CONST: + case CONST_STR: + case CONST_CLASS: + case CMP_L: + case CMP_G: + return false; + + default: + return true; + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 124e3458e..bb1a02dd3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -329,16 +329,24 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { int offset = aTry.getStartAddress(); int end = offset + aTry.getInstructionCount() - 1; - InsnNode insn = insnByOffset[offset]; - insn.add(AFlag.TRY_ENTER); + boolean tryBlockStarted = false; + InsnNode insn = null; while (offset <= end && offset >= 0) { insn = insnByOffset[offset]; - catchBlock.addInsn(insn); + if (insn != null) { + if (tryBlockStarted) { + catchBlock.addInsn(insn); + } else if (insn.canThrowException()) { + insn.add(AFlag.TRY_ENTER); + catchBlock.addInsn(insn); + tryBlockStarted = true; + } + } offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); } if (insnByOffset[end] != null) { insnByOffset[end].add(AFlag.TRY_LEAVE); - } else { + } else if (insn != null) { insn.add(AFlag.TRY_LEAVE); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index b0d77f587..af47e5973 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -51,11 +51,10 @@ public class ClassModifier extends AbstractVisitor { cls.add(AFlag.DONT_GENERATE); return false; } - removeSyntheticFields(cls); - cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth)); - cls.getMethods().forEach(ClassModifier::removeEmptyMethods); - markAnonymousClass(cls); + removeSyntheticFields(cls); + cls.getMethods().forEach(ClassModifier::removeSyntheticMethods); + cls.getMethods().forEach(ClassModifier::removeEmptyMethods); return false; } @@ -69,29 +68,36 @@ public class ClassModifier extends AbstractVisitor { private void markAnonymousClass(ClassNode cls) { if (cls.isAnonymous()) { cls.add(AFlag.ANONYMOUS_CLASS); + cls.add(AFlag.DONT_GENERATE); } } + /** + * Remove synthetic fields if type is outer class or class will be inlined (anonymous) + */ private static void removeSyntheticFields(ClassNode cls) { - if (!cls.getClassInfo().isInner() || cls.getAccessFlags().isStatic()) { + if (cls.getAccessFlags().isStatic()) { return; } - // remove fields if it is synthetic and type is a outer class - for (FieldNode field : cls.getFields()) { - if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { - ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); - ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); - ClassInfo parentClass = cls.getClassInfo().getParentClass(); - if (fieldsCls != null && parentClass.equals(fieldsCls.getClassInfo())) { - int found = 0; - for (MethodNode mth : cls.getMethods()) { - if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) { - found++; + boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS); + if (inline || cls.getClassInfo().isInner()) { + for (FieldNode field : cls.getFields()) { + if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { + ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType()); + ClassNode fieldsCls = cls.dex().resolveClass(clsInfo); + ClassInfo parentClass = cls.getClassInfo().getParentClass(); + if (fieldsCls != null + && (inline || parentClass.equals(fieldsCls.getClassInfo()))) { + int found = 0; + for (MethodNode mth : cls.getMethods()) { + if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) { + found++; + } + } + if (found != 0) { + field.addAttr(new FieldReplaceAttr(fieldsCls.getClassInfo())); + field.add(AFlag.DONT_GENERATE); } - } - if (found != 0) { - field.addAttr(new FieldReplaceAttr(parentClass)); - field.add(AFlag.DONT_GENERATE); } } } @@ -137,7 +143,7 @@ public class ClassModifier extends AbstractVisitor { return true; } - private static void removeSyntheticMethods(ClassNode cls, MethodNode mth) { + private static void removeSyntheticMethods(MethodNode mth) { if (mth.isNoCode()) { return; } @@ -145,6 +151,7 @@ public class ClassModifier extends AbstractVisitor { if (!af.isSynthetic()) { return; } + ClassNode cls = mth.getParentClass(); if (removeBridgeMethod(cls, mth)) { if (Consts.DEBUG) { mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method"); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java index 92cffd845..bc0254389 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DependencyCollector.java @@ -18,6 +18,7 @@ import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.utils.exceptions.JadxException; public class DependencyCollector extends AbstractVisitor { @@ -41,6 +42,12 @@ public class DependencyCollector extends AbstractVisitor { } for (FieldNode fieldNode : cls.getFields()) { addDep(dex, depList, fieldNode.getType()); + + // process instructions from field init + FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT); + if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) { + processInsn(dex, depList, fieldInitAttr.getInsn()); + } } // TODO: process annotations and generics for (MethodNode methodNode : cls.getMethods()) { diff --git a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java index f4a9b5d0a..5ded8fe65 100644 --- a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java @@ -5,6 +5,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import org.jetbrains.annotations.Nullable; + import jadx.core.dex.attributes.AType; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.BlockNode; @@ -136,7 +138,10 @@ public class RegionUtils { return !notEmpty(container); } - public static boolean notEmpty(IContainer container) { + public static boolean notEmpty(@Nullable IContainer container) { + if (container == null) { + return false; + } if (container instanceof IBlock) { return !((IBlock) container).getInstructions().isEmpty(); } else if (container instanceof IRegion) { diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index 53d49dfca..3efb5125a 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -1,18 +1,23 @@ package jadx.core.utils.android; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.android.dx.rop.code.AccessFlags; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.codegen.ClassGen; import jadx.core.codegen.CodeWriter; +import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AType; +import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.info.ConstStorage; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; @@ -86,6 +91,7 @@ public class AndroidResourcesUtils { } private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) { + Map resFieldsMap = fillResFieldsMap(resCls); Map innerClsMap = new TreeMap<>(); if (rClsExists) { for (ClassNode innerClass : resCls.getInnerClasses()) { @@ -93,18 +99,20 @@ public class AndroidResourcesUtils { } } for (ResourceEntry resource : resStorage.getResources()) { - ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> { - ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + name, - AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); - resCls.addInnerClass(newTypeCls); - if (rClsExists) { - newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); - } - return newTypeCls; - }); - FieldNode rField = typeCls.searchFieldByName(resource.getKeyName()); + final String resTypeName = resource.getTypeName(); + ClassNode typeCls = innerClsMap.computeIfAbsent( + resTypeName, + name -> addClassForResType(resCls, rClsExists, name) + ); + final String resName; + if ("style".equals(resTypeName)) { + resName = resource.getKeyName().replace('.', '_'); + } else { + resName = resource.getKeyName(); + } + FieldNode rField = typeCls.searchFieldByName(resName); if (rField == null) { - FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resource.getKeyName(), ArgType.INT); + FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT); rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); rField.addAttr(FieldInitAttr.constValue(resource.getId())); typeCls.getFields().add(rField); @@ -112,6 +120,42 @@ public class AndroidResourcesUtils { rField.addAttr(AType.COMMENTS, "added by JADX"); } } + FieldNode fieldNode = resFieldsMap.get(resource.getId()); + if (fieldNode != null + && !fieldNode.getName().equals(resName) + && NameMapper.isValidIdentifier(resName)) { + fieldNode.getFieldInfo().setAlias(resName); + } } } + + @NotNull + private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) { + ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + typeName, + AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); + resCls.addInnerClass(newTypeCls); + if (rClsExists) { + newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); + } + return newTypeCls; + } + + @NotNull + private static Map fillResFieldsMap(ClassNode resCls) { + Map resFieldsMap = new HashMap<>(); + ConstStorage constStorage = resCls.root().getConstValues(); + Map constFields = constStorage.getGlobalConstFields(); + for (Map.Entry entry : constFields.entrySet()) { + Object key = entry.getKey(); + FieldNode field = entry.getValue(); + AccessInfo accessFlags = field.getAccessFlags(); + if (field.getType().equals(ArgType.INT) + && accessFlags.isStatic() + && accessFlags.isFinal() + && key instanceof Integer) { + resFieldsMap.put((Integer) key, field); + } + } + return resFieldsMap; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java index 0d2cd18e8..470d26118 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java @@ -59,8 +59,8 @@ public class InputFile { loadFromZip(".dex"); return; } - if (fileName.endsWith(".jar")) { - // check if jar contains '.dex' files + if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) { + // check if jar/aar contains '.dex' files if (loadFromZip(".dex")) { return; } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index 63b730a96..d0dedc8ac 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -15,8 +15,6 @@ import org.slf4j.LoggerFactory; import jadx.api.ResourcesLoader; import jadx.core.codegen.CodeWriter; import jadx.core.dex.info.ConstStorage; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -40,7 +38,6 @@ public class BinaryXMLParser extends CommonBinaryParser { private static final boolean ATTR_NEW_LINE = false; private final Map styleMap = new HashMap<>(); - private final Map localStyleMap = new HashMap<>(); private final Map resNames; private final Map nsMap = new HashMap<>(); private Set nsMapGenerated; @@ -63,16 +60,7 @@ public class BinaryXMLParser extends CommonBinaryParser { this.rootNode = rootNode; try { readAndroidRStyleClass(); - // add application constants ConstStorage constStorage = rootNode.getConstValues(); - Map constFields = constStorage.getGlobalConstFields(); - for (Map.Entry entry : constFields.entrySet()) { - Object key = entry.getKey(); - FieldNode field = entry.getValue(); - if (field.getType().equals(ArgType.INT) && key instanceof Integer) { - localStyleMap.put((Integer) key, field); - } - } resNames = constStorage.getResourcesNames(); } catch (Exception e) { throw new JadxRuntimeException("BinaryXMLParser init error", e); @@ -381,38 +369,27 @@ public class BinaryXMLParser extends CommonBinaryParser { private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData, String shortNsName, String attrName) { - if (attrValDataType == TYPE_REFERENCE) { // reference custom processing String name = styleMap.get(attrValData); if (name != null) { writer.add("@style/").add(name.replaceAll("_", ".")); } else { - FieldNode field = localStyleMap.get(attrValData); - if (field != null) { - String cls = field.getParentClass().getShortName().toLowerCase(); + String resName = resNames.get(attrValData); + if (resName != null) { writer.add("@"); - if ("id".equals(cls)) { - writer.add('+'); + if (resName.startsWith("id/")) { + writer.add("+"); } - writer.add(cls).add("/").add(field.getName()); + writer.add(resName); } else { - String resName = resNames.get(attrValData); + resName = ValuesParser.getAndroidResMap().get(attrValData); if (resName != null) { - writer.add("@"); - if (resName.startsWith("id/")) { - writer.add("+"); - } - writer.add(resName); + writer.add("@android:").add(resName); + } else if (attrValData == 0) { + writer.add("@null"); } else { - resName = ValuesParser.getAndroidResMap().get(attrValData); - if (resName != null) { - writer.add("@android:").add(resName); - } else if (attrValData == 0) { - writer.add("@null"); - } else { - writer.add("0x").add(Integer.toHexString(attrValData)); - } + writer.add("0x").add(Integer.toHexString(attrValData)); } } } diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 6ff935f79..1070345f0 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -140,7 +140,7 @@ public abstract class IntegrationTest extends TestUtils { protected void decompile(JadxDecompiler jadx, ClassNode cls) { List passes = JadxInternalAccess.getPassList(jadx); - ProcessClass.process(cls, passes, new CodeGen()); + ProcessClass.process(cls, passes, true); } protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) { @@ -154,7 +154,7 @@ public abstract class IntegrationTest extends TestUtils { protected void generateClsCode(ClassNode cls) { try { - new CodeGen().visit(cls); + CodeGen.generate(cls); } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); diff --git a/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java b/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java index 199d6e250..d07e1511a 100644 --- a/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java +++ b/jadx-core/src/test/java/jadx/tests/api/compiler/ClassFileManager.java @@ -6,10 +6,8 @@ import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import java.io.IOException; import java.security.SecureClassLoader; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import static javax.tools.JavaFileObject.Kind; diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 02ffdc13c..a13027d35 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -15,12 +15,11 @@ import jadx.api.JadxArgs; import jadx.api.JadxDecompiler; import jadx.api.JadxInternalAccess; import jadx.api.JavaClass; -import jadx.core.codegen.CodeGen; +import jadx.core.ProcessClass; import jadx.core.codegen.CodeWriter; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.tests.api.IntegrationTest; @@ -99,15 +98,10 @@ public abstract class BaseExternalTest extends IntegrationTest { if (!decompile) { return false; } - -// ProcessClass.process(classNode, passes, new CodeGen()); - for (IDexTreeVisitor visitor : passes) { - DepthTraversal.visit(visitor, classNode); - } try { - new CodeGen().visit(classNode); + ProcessClass.process(classNode, passes, true); } catch (Exception e) { - throw new JadxRuntimeException("Codegen failed", e); + throw new JadxRuntimeException("Class process failed", e); } LOG.info("----------------------------------------------------------------"); LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex()); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java index 1560806a6..1a9a88021 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassFakeSyntheticConstructor.java @@ -3,7 +3,6 @@ package jadx.tests.integration.inner; import org.junit.Test; import jadx.core.dex.nodes.ClassNode; -import jadx.tests.api.IntegrationTest; import jadx.tests.api.SmaliTest; import static jadx.tests.api.utils.JadxMatchers.containsOne; diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java index 6deb70ef8..7431c5eb0 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestInnerClassSyntheticConstructor.java @@ -3,10 +3,6 @@ package jadx.tests.integration.inner; import org.junit.Test; import jadx.tests.api.IntegrationTest; -import jadx.tests.api.SmaliTest; - -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertThat; public class TestInnerClassSyntheticConstructor extends IntegrationTest { diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestEndlessLoop.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestEndlessLoop.java index 28dedc5e6..cc463ed7f 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestEndlessLoop.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestEndlessLoop.java @@ -5,7 +5,6 @@ import org.junit.Test; import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; diff --git a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchReturnFromCase2.java b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchReturnFromCase2.java index 63df67722..383820ade 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchReturnFromCase2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/switches/TestSwitchReturnFromCase2.java @@ -4,9 +4,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; import org.junit.Test; -import static jadx.tests.api.utils.JadxMatchers.containsOne; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchStartOnMove.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchStartOnMove.java new file mode 100644 index 000000000..468a3a243 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchStartOnMove.java @@ -0,0 +1,33 @@ +package jadx.tests.integration.trycatch; + +import org.junit.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestTryCatchStartOnMove extends SmaliTest { + +// private static void test(String s) { +// try { +// call(s); +// } catch (Exception unused) { +// System.out.println("Failed call for " + s); +// } +// } +// +// private static void call(String s) { +// } + + @Test + public void test() { + ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryCatchStartOnMove"); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("try {")); + assertThat(code, containsOne("} catch (Exception e) {")); + assertThat(code, containsOne("System.out.println(\"Failed call for \" + str")); + } +} diff --git a/jadx-core/src/test/smali/trycatch/TestTryCatchStartOnMove.smali b/jadx-core/src/test/smali/trycatch/TestTryCatchStartOnMove.smali new file mode 100644 index 000000000..d09dffbee --- /dev/null +++ b/jadx-core/src/test/smali/trycatch/TestTryCatchStartOnMove.smali @@ -0,0 +1,45 @@ +.class public Ltrycatch/TestTryCatchStartOnMove; +.super Ljava/lang/Object; + + +# direct methods +.method private static test(Ljava/lang/String;)V + .registers 5 + + :try_start + move v3, p0 + invoke-static {v3}, Ltrycatch/TestTryCatchStartOnMove;->call(Ljava/lang/String;)V + :try_end + .catch Ljava/lang/Exception; {:try_start .. :try_end} :catch + + :goto_ret + return-void + + :catch + move-exception v0 + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + new-instance v1, Ljava/lang/StringBuilder; + invoke-direct {v1}, Ljava/lang/StringBuilder;->()V + const-string v2, "Failed call for " + invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + move-result-object v1 + invoke-virtual {v1, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + move-result-object v1 + invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + move-result-object v1 + invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + goto :goto_ret +.end method + + +.method public constructor ()V + .registers 1 + invoke-direct {p0}, Ljadx/tests/api/SmaliTest;->()V + return-void +.end method + + +.method private static call(Ljava/lang/String;)V + .registers 1 + return-void +.end method diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index d4becc1fc..6ba64d8fe 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -1,6 +1,6 @@ plugins { - id 'edu.sc.seis.launch4j' version '2.4.4' - id 'com.github.johnrengelman.shadow' version '4.0.3' + id 'edu.sc.seis.launch4j' version '2.4.5' + id 'com.github.johnrengelman.shadow' version '5.0.0' } apply plugin: 'application' @@ -13,7 +13,8 @@ targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compile(project(":jadx-core")) compile(project(":jadx-cli")) - compile 'com.fifesoft:rsyntaxtextarea:3.0.0' + + compile 'com.fifesoft:rsyntaxtextarea:3.0.2' compile 'com.google.code.gson:gson:2.8.5' compile files('libs/jfontchooser-1.0.5.jar') compile 'hu.kazocsaba:image-viewer:1.2.3' @@ -21,9 +22,9 @@ dependencies { compile 'org.apache.commons:commons-lang3:3.8.1' compile 'org.apache.commons:commons-text:1.6' - compile 'io.reactivex.rxjava2:rxjava:2.2.5' - compile "com.github.akarnokd:rxjava2-swing:0.3.3" - compile 'com.android.tools.build:apksig:3.3.0' + compile 'io.reactivex.rxjava2:rxjava:2.2.7' + compile "com.github.akarnokd:rxjava2-swing:0.3.4" + compile 'com.android.tools.build:apksig:3.3.2' } applicationDistribution.with { diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 52ec3908b..69bd82107 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -1,12 +1,15 @@ package jadx.gui; -import javax.swing.*; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.Collectors; +import javax.swing.ProgressMonitor; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,14 +81,13 @@ public class JadxWrapper { */ public List getIncludedClasses() { List classList = decompiler.getClasses(); - String excludedPackages = settings.getExcludedPackages().trim(); - if (excludedPackages.length() == 0) { + List excludedPackages = getExcludedPackages(); + if (excludedPackages.isEmpty()) { return classList; } - String[] excluded = excludedPackages.split("[ ]+"); return classList.stream().filter(cls -> { - for (String exclude : excluded) { + for (String exclude : excludedPackages) { if (cls.getFullName().startsWith(exclude)) { return false; } @@ -94,6 +96,23 @@ public class JadxWrapper { }).collect(Collectors.toList()); } + public List getExcludedPackages() { + String excludedPackages = settings.getExcludedPackages().trim(); + return Arrays.asList(excludedPackages.split("[ ]+")); + } + + public void addExcludedPackage(String packageToExclude) { + settings.setExcludedPackages(settings.getExcludedPackages() + ' ' + packageToExclude); + settings.sync(); + } + + public void removeExcludedPackage(String packageToRemoveFromExclusion) { + List list = new ArrayList<>(getExcludedPackages()); + list.remove(packageToRemoveFromExclusion); + settings.setExcludedPackages(String.join(" ", list)); + settings.sync(); + } + public List getPackages() { return decompiler.getPackages(); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index f0c190229..7a7c04c33 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -333,4 +333,5 @@ public class JadxSettings extends JadxCLIArgs { settingsVersion = CURRENT_SETTINGS_VERSION; sync(); } + } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 81579a996..c2252a9a7 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -247,10 +247,14 @@ public class JadxSettingsWindow extends JDialog { JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button")); editExcludedPackages.addActionListener(event -> { + String oldExcludedPackages = settings.getExcludedPackages(); String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"), settings.getExcludedPackages()); if (result != null) { settings.setExcludedPackages(result); + if (!oldExcludedPackages.equals(result)) { + needReload(); + } } }); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index bfb8ce6bc..8481bbbcb 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -1,13 +1,16 @@ package jadx.gui.treemodel; -import javax.swing.*; import java.util.ArrayList; import java.util.List; +import javax.swing.Icon; +import javax.swing.ImageIcon; + import org.jetbrains.annotations.NotNull; import jadx.api.JavaClass; import jadx.api.JavaPackage; +import jadx.gui.JadxWrapper; import jadx.gui.utils.Utils; public class JPackage extends JNode implements Comparable { @@ -15,12 +18,16 @@ public class JPackage extends JNode implements Comparable { private static final ImageIcon PACKAGE_ICON = Utils.openIcon("package_obj"); + private final String fullName; private String name; + private boolean enabled; private final List classes; private final List innerPackages = new ArrayList<>(1); - public JPackage(JavaPackage pkg) { + public JPackage(JavaPackage pkg, JadxWrapper wrapper) { + this.fullName = pkg.getName(); this.name = pkg.getName(); + setEnabled(wrapper); List javaClasses = pkg.getClasses(); this.classes = new ArrayList<>(javaClasses.size()); for (JavaClass javaClass : javaClasses) { @@ -29,20 +36,30 @@ public class JPackage extends JNode implements Comparable { update(); } - public JPackage(String name) { + public JPackage(String name, JadxWrapper wrapper) { + this.fullName = name; this.name = name; + setEnabled(wrapper); this.classes = new ArrayList<>(1); } + private void setEnabled(JadxWrapper wrapper) { + List excludedPackages = wrapper.getExcludedPackages(); + this.enabled = excludedPackages.isEmpty() + || excludedPackages.stream().filter(p -> !p.isEmpty()).noneMatch(p -> name.startsWith(p)); + } + public final void update() { removeAllChildren(); - for (JPackage pkg : innerPackages) { - pkg.update(); - add(pkg); - } - for (JClass cls : classes) { - cls.update(); - add(cls); + if (isEnabled()) { + for (JPackage pkg : innerPackages) { + pkg.update(); + add(pkg); + } + for (JClass cls : classes) { + cls.update(); + add(cls); + } } } @@ -51,6 +68,10 @@ public class JPackage extends JNode implements Comparable { return name; } + public String getFullName() { + return fullName; + } + public void setName(String name) { this.name = name; } @@ -108,4 +129,8 @@ public class JPackage extends JNode implements Comparable { public String makeLongString() { return name; } + + public boolean isEnabled() { + return enabled; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 753af2523..59a82b20b 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -3,6 +3,7 @@ package jadx.gui.treemodel; import javax.swing.*; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -68,6 +69,14 @@ public class JResource extends JLoadableNode implements Comparable { } } else { removeAllChildren(); + + Comparator typeComparator + = (r1, r2) -> r1.type.ordinal() - r2.type.ordinal(); + Comparator nameComparator + = Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER); + + files.sort(typeComparator.thenComparing(nameComparator)); + for (JResource res : files) { res.update(); add(res); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java index d6541fb8c..09a66d91d 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java @@ -33,7 +33,7 @@ public class JSources extends JNode { removeAllChildren(); if (flatPackages) { for (JavaPackage pkg : wrapper.getPackages()) { - add(new JPackage(pkg)); + add(new JPackage(pkg, wrapper)); } } else { // build packages hierarchy @@ -54,7 +54,7 @@ public class JSources extends JNode { List getHierarchyPackages(List packages) { Map pkgMap = new HashMap<>(); for (JavaPackage pkg : packages) { - addPackage(pkgMap, new JPackage(pkg)); + addPackage(pkgMap, new JPackage(pkg, wrapper)); } // merge packages without classes boolean repeat; @@ -114,7 +114,7 @@ public class JSources extends JNode { pkg.setName(shortName); JPackage prevPkg = pkgs.get(prevPart); if (prevPkg == null) { - prevPkg = new JPackage(prevPart); + prevPkg = new JPackage(prevPart, wrapper); addPackage(pkgs, prevPkg); } prevPkg.getInnerPackages().add(pkg); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java index 9be61b539..4659aab39 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/AboutDialog.java @@ -2,9 +2,11 @@ package jadx.gui.ui; import javax.swing.*; import java.awt.*; +import java.net.URL; import jadx.api.JadxDecompiler; import jadx.gui.utils.NLS; +import jadx.gui.utils.Utils; class AboutDialog extends JDialog { private static final long serialVersionUID = 5763493590584039096L; @@ -16,7 +18,10 @@ class AboutDialog extends JDialog { public final void initUI() { Font font = new Font("Serif", Font.BOLD, 13); - JLabel name = new JLabel("jadx"); + URL logoURL = getClass().getResource("/logos/jadx-logo-48px.png"); + Icon logo = new ImageIcon(logoURL, "jadx logo"); + + JLabel name = new JLabel("jadx", logo, SwingConstants.CENTER); name.setFont(font); name.setAlignmentX(0.5f); @@ -24,10 +29,24 @@ class AboutDialog extends JDialog { desc.setFont(font); desc.setAlignmentX(0.5f); - JLabel version = new JLabel("version: " + JadxDecompiler.getVersion()); + JLabel version = new JLabel("jadx version: " + JadxDecompiler.getVersion()); version.setFont(font); version.setAlignmentX(0.5f); + String javaVm = System.getProperty("java.vm.name"); + String javaVer = System.getProperty("java.vm.version"); + + javaVm = javaVm == null ? "" : javaVm; + + JLabel javaVmLabel = new JLabel("Java VM: " + javaVm); + javaVmLabel.setFont(font); + javaVmLabel.setAlignmentX(0.5f); + + javaVer = javaVer == null ? "" : javaVer; + JLabel javaVerLabel = new JLabel("Java version: " + javaVer); + javaVerLabel.setFont(font); + javaVerLabel.setAlignmentX(0.5f); + JPanel textPane = new JPanel(); textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS)); @@ -38,6 +57,9 @@ class AboutDialog extends JDialog { textPane.add(Box.createRigidArea(new Dimension(0, 10))); textPane.add(version); textPane.add(Box.createRigidArea(new Dimension(0, 20))); + textPane.add(javaVmLabel); + textPane.add(javaVerLabel); + textPane.add(Box.createRigidArea(new Dimension(0, 20))); JButton close = new JButton(NLS.str("tabs.close")); close.addActionListener(event -> dispose()); @@ -47,6 +69,8 @@ class AboutDialog extends JDialog { contentPane.add(textPane, BorderLayout.CENTER); contentPane.add(close, BorderLayout.PAGE_END); + Utils.setWindowIcons(this); + setModalityType(ModalityType.APPLICATION_MODAL); setTitle(NLS.str("about_dialog.title")); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 4885eb93e..608e50c66 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -1,6 +1,47 @@ package jadx.gui.ui; -import javax.swing.*; +import static javax.swing.KeyStroke.getKeyStroke; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.DisplayMode; +import java.awt.Font; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTarget; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileInputStream; +import java.util.Arrays; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; +import javax.swing.JTree; +import javax.swing.ProgressMonitor; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.TreeExpansionEvent; @@ -12,22 +53,6 @@ import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; -import java.awt.*; -import java.awt.dnd.DnDConstants; -import java.awt.dnd.DropTarget; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.File; -import java.io.FileInputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; import org.fife.ui.rsyntaxtextarea.Theme; import org.slf4j.Logger; @@ -46,6 +71,7 @@ import jadx.gui.treemodel.JCertificate; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JLoadableNode; import jadx.gui.treemodel.JNode; +import jadx.gui.treemodel.JPackage; import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JRoot; import jadx.gui.update.JadxUpdate; @@ -57,8 +83,6 @@ import jadx.gui.utils.Link; import jadx.gui.utils.NLS; import jadx.gui.utils.Utils; -import static javax.swing.KeyStroke.getKeyStroke; - @SuppressWarnings("serial") public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); @@ -82,6 +106,7 @@ public class MainWindow extends JFrame { private static final ImageIcon ICON_PREF = Utils.openIcon("wrench"); private static final ImageIcon ICON_DEOBF = Utils.openIcon("lock_edit"); private static final ImageIcon ICON_LOG = Utils.openIcon("report"); + private static final ImageIcon ICON_JADX = Utils.openIcon("jadx-logo"); private final transient JadxWrapper wrapper; private final transient JadxSettings settings; @@ -116,16 +141,7 @@ public class MainWindow extends JFrame { registerBundledFonts(); initUI(); initMenuAndToolbar(); - setWindowIcons(); - } - - private void setWindowIcons() { - List icons = new ArrayList<>(); - icons.add(Utils.openImage("/logos/jadx-logo-16px.png")); - icons.add(Utils.openImage("/logos/jadx-logo-32px.png")); - icons.add(Utils.openImage("/logos/jadx-logo-48px.png")); - icons.add(Utils.openImage("/logos/jadx-logo.png")); - setIconImages(icons); + Utils.setWindowIcons(this); loadSettings(); checkForUpdate(); } @@ -323,6 +339,14 @@ public class MainWindow extends JFrame { } } + private void treeRightClickAction(MouseEvent e) { + Object obj = tree.getLastSelectedPathComponent(); + if (obj instanceof JPackage) { + JPackagePopUp menu = new JPackagePopUp((JPackage) obj); + menu.show(e.getComponent(), e.getX(), e.getY()); + } + } + private void syncWithEditor() { ContentPanel selectedContentPanel = tabbedPane.getSelectedCodePanel(); if (selectedContentPanel == null) { @@ -462,7 +486,7 @@ public class MainWindow extends JFrame { logAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); - Action aboutAction = new AbstractAction(NLS.str("menu.about")) { + Action aboutAction = new AbstractAction(NLS.str("menu.about"), ICON_JADX) { @Override public void actionPerformed(ActionEvent e) { new AboutDialog().setVisible(true); @@ -582,7 +606,12 @@ public class MainWindow extends JFrame { tree.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - treeClickAction(); + if (SwingUtilities.isRightMouseButton(e)) { + treeRightClickAction(e); + } + else { + treeClickAction(); + } } }); tree.addKeyListener(new KeyAdapter() { @@ -602,6 +631,9 @@ public class MainWindow extends JFrame { if (value instanceof JNode) { setIcon(((JNode) value).getIcon()); } + if (value instanceof JPackage) { + setEnabled(((JPackage) value).isEnabled()); + } return c; } }); @@ -748,4 +780,23 @@ public class MainWindow extends JFrame { public void menuCanceled(MenuEvent e) { } } + + private class JPackagePopUp extends JPopupMenu { + JMenuItem excludeItem = new JCheckBoxMenuItem("Exclude"); + + public JPackagePopUp(JPackage pkg) { + excludeItem.setSelected(!pkg.isEnabled()); + add(excludeItem); + excludeItem.addItemListener(e -> { + String fullName = pkg.getFullName(); + if (excludeItem.isSelected()) { + wrapper.addExcludedPackage(fullName); + } + else { + wrapper.removeExcludedPackage(fullName); + } + reOpenFile(); + }); + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java index f6f724273..8ca0bb926 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java @@ -7,6 +7,8 @@ import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.io.InputStream; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -191,4 +193,13 @@ public class Utils { } return sb.toString().trim(); } + + public static void setWindowIcons(Window window) { + List icons = new ArrayList<>(); + icons.add(Utils.openImage("/logos/jadx-logo-16px.png")); + icons.add(Utils.openImage("/logos/jadx-logo-32px.png")); + icons.add(Utils.openImage("/logos/jadx-logo-48px.png")); + icons.add(Utils.openImage("/logos/jadx-logo.png")); + window.setIconImages(icons); + } } diff --git a/jadx-gui/src/main/resources/icons-16/jadx-logo.png b/jadx-gui/src/main/resources/icons-16/jadx-logo.png new file mode 100644 index 000000000..a9a2d0856 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/jadx-logo.png differ diff --git a/jadx-gui/src/main/resources/logos/jadx-logo.ico b/jadx-gui/src/main/resources/logos/jadx-logo.ico index 1f02bd689..85786cd6b 100644 Binary files a/jadx-gui/src/main/resources/logos/jadx-logo.ico and b/jadx-gui/src/main/resources/logos/jadx-logo.ico differ diff --git a/jadx-gui/src/main/resources/logos/jadx-logo.svg b/jadx-gui/src/main/resources/logos/jadx-logo.svg new file mode 100644 index 000000000..bc0e0b2e8 --- /dev/null +++ b/jadx-gui/src/main/resources/logos/jadx-logo.svg @@ -0,0 +1 @@ +jadxlogoDXJA \ No newline at end of file diff --git a/scripts/bintray-upload.sh b/scripts/bintray-upload.sh index 3a6a35783..a8982f981 100644 --- a/scripts/bintray-upload.sh +++ b/scripts/bintray-upload.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash -set -xe +set -e export JFROG_CLI_OFFER_CONFIG=false export JFROG_CLI_LOG_LEVEL=DEBUG npm install -g jfrog-cli-go -TARGET=skylot/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION} -CREDENTIALS="--user=skylot --key=${BINTRAY_KEY}" +TARGET=${BINTRAY_USER}/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION} +CREDENTIALS="--user=${BINTRAY_USER} --key=${BINTRAY_KEY}" jfrog bt version-create ${TARGET} ${CREDENTIALS} --desc=${JADX_VERSION} jfrog bt upload 'build/jadx.*\.(zip|exe)' ${TARGET} ${CREDENTIALS} --regexp=true --publish=true diff --git a/scripts/travis-master.sh b/scripts/travis-master.sh index 75c7b0da0..07c77ec7f 100644 --- a/scripts/travis-master.sh +++ b/scripts/travis-master.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -xe +set -e # upload coverage to codecov ./gradlew clean build jacocoTestReport diff --git a/scripts/travis-release.sh b/scripts/travis-release.sh index bdad6ef2f..49c7ba64f 100644 --- a/scripts/travis-release.sh +++ b/scripts/travis-release.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -xe +set -e npm install -g semantic-release npm install -g semantic-release/exec