diff --git a/.travis.yml b/.travis.yml
index 4dbcf3303..6e13ba0f8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,6 @@ git:
depth: false
before_install:
- - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
- chmod +x gradlew
# override install to skip 'gradle assemble'
@@ -26,6 +25,8 @@ matrix:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
+ - env: JDK=openjdk11
+ jdk: openjdk11
script:
- java -version
diff --git a/README.md b/README.md
index 387710a3c..cadd85d3d 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,13 @@ After download unpack zip file go to `bin` directory and run:
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
-### Building from source
-Java 8 JDK or higher must be installed:
+
+### Related projects:
+- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
+
+
+### Building jadx from source
+JDK 8 or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
diff --git a/build.gradle b/build.gradle
index 48299476c..d0aef43b7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,5 @@
plugins {
- id 'com.github.ksoichiro.console.reporter' version '0.5.0'
- id 'org.sonarqube' version '2.6.2'
+ id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.20.0'
}
@@ -12,7 +11,6 @@ allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
- apply plugin: 'com.github.ksoichiro.console.reporter'
version = jadxVersion
@@ -41,10 +39,9 @@ allprojects {
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
- testCompile 'org.hamcrest:hamcrest-library:1.3'
- testCompile 'org.mockito:mockito-core:2.20.1'
+ testCompile 'org.hamcrest:hamcrest-library:2.1'
+ testCompile 'org.mockito:mockito-core:2.23.4'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
- testCompile 'cglib:cglib-nodep:3.2.7'
}
repositories {
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a95009c3b..558870dad 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-4.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle
index fe8bc4327..edb6beb52 100644
--- a/jadx-core/build.gradle
+++ b/jadx-core/build.gradle
@@ -5,13 +5,13 @@ dependencies {
compile files('lib/dx-1.16.jar')
compile 'commons-io:commons-io:2.6'
- compile 'org.ow2.asm:asm:6.2'
- compile 'org.jetbrains:annotations:16.0.2'
- compile 'uk.com.robust-it:cloning:1.9.10'
+ compile 'org.ow2.asm:asm:7.0'
+ compile 'org.jetbrains:annotations:16.0.3'
+ compile 'uk.com.robust-it:cloning:1.9.11'
- testCompile 'org.smali:smali:2.2.4'
- testCompile 'org.smali:baksmali:2.2.4'
+ testCompile 'org.smali:smali:2.2.5'
+ testCompile 'org.smali:baksmali:2.2.5'
- testCompile 'org.apache.commons:commons-lang3:3.7'
+ testCompile 'org.apache.commons:commons-lang3:3.8.1'
}
diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
index 61ab3b013..5dcd474f3 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
@@ -4,6 +4,8 @@ import java.util.Iterator;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
+import jadx.core.dex.info.ClassInfo;
+import jadx.core.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -165,8 +167,16 @@ public class MethodGen {
addFallbackMethodCode(code);
code.startLine("*/");
+ ClassInfo clsAlias = mth.getParentClass().getAlias();
+
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
- .add(mth.toString())
+ .add(clsAlias.makeFullClsName(clsAlias.getShortName(), true))
+ .add(".")
+ .add(mth.getAlias())
+ .add("(")
+ .add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
+ .add("):")
+ .add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
} else {
RegionGen regionGen = new RegionGen(this);
diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java b/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java
new file mode 100644
index 000000000..f5b8a84ee
--- /dev/null
+++ b/jadx-core/src/main/java/jadx/core/dex/instructions/CallMthInterface.java
@@ -0,0 +1,8 @@
+package jadx.core.dex.instructions;
+
+import jadx.core.dex.info.MethodInfo;
+
+public interface CallMthInterface {
+
+ public MethodInfo getCallMth();
+}
diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java
index 5baa632cd..217fa08e5 100644
--- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java
@@ -9,7 +9,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
-public class InvokeNode extends InsnNode {
+public class InvokeNode extends InsnNode implements CallMthInterface {
private final InvokeType type;
private final MethodInfo mth;
diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java
index 0b3a4e80c..9a39665ae 100644
--- a/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java
+++ b/jadx-core/src/main/java/jadx/core/dex/instructions/mods/ConstructorInsn.java
@@ -2,13 +2,14 @@ package jadx.core.dex.instructions.mods;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
+import jadx.core.dex.instructions.CallMthInterface;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
-public class ConstructorInsn extends InsnNode {
+public class ConstructorInsn extends InsnNode implements CallMthInterface {
private final MethodInfo callMth;
private final CallType callType;
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java
index fcab32313..0aa83e114 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java
@@ -4,19 +4,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import jadx.core.dex.instructions.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
-import jadx.core.dex.instructions.ArithNode;
-import jadx.core.dex.instructions.ArithOp;
-import jadx.core.dex.instructions.ConstStringNode;
-import jadx.core.dex.instructions.IfNode;
-import jadx.core.dex.instructions.IndexInsnNode;
-import jadx.core.dex.instructions.InsnType;
-import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
@@ -148,6 +142,15 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
+ /**
+ * Simplify chains of calls to StringBuilder#append() plus constructor of StringBuilder.
+ * Those chains are usually automatically generated by the Java compiler when you create String
+ * concatenations like "text " + 1 + " text".
+ *
+ * @param mth
+ * @param insn
+ * @return
+ */
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
@@ -202,7 +205,15 @@ public class SimplifyVisitor extends AbstractVisitor {
}
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
- concatInsn.addArg(chain.get(argInd).getArg(1));
+ InsnNode node = chain.get(argInd);
+ MethodInfo method = ((CallMthInterface) node).getCallMth();
+ if (!(node.getArgsCount() < 2 && method.isConstructor() || method.getName().equals("append"))) {
+ // The chain contains other calls to StringBuilder methods than the constructor or append.
+ // We can't simplify such chains, therefore we leave them as they are.
+ return null;
+ }
+ // process only constructor and append() calls
+ concatInsn.addArg(node.getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/CountString.java b/jadx-core/src/test/java/jadx/tests/api/utils/CountString.java
index 489796137..58382a41e 100644
--- a/jadx-core/src/test/java/jadx/tests/api/utils/CountString.java
+++ b/jadx-core/src/test/java/jadx/tests/api/utils/CountString.java
@@ -1,25 +1,21 @@
package jadx.tests.api.utils;
import org.hamcrest.Description;
-import org.hamcrest.core.SubstringMatcher;
+import org.hamcrest.TypeSafeMatcher;
-public class CountString extends SubstringMatcher {
+public class CountString extends TypeSafeMatcher {
private final int count;
+ private final String substring;
public CountString(int count, String substring) {
- super(substring);
this.count = count;
+ this.substring = substring;
}
@Override
- protected boolean evalSubstringOf(String string) {
- return this.count == count(string);
- }
-
- @Override
- protected String relationship() {
- return "containing <" + count + "> occurrence of";
+ protected boolean matchesSafely(String item) {
+ return this.count == count(item);
}
@Override
@@ -27,7 +23,12 @@ public class CountString extends SubstringMatcher {
mismatchDescription.appendText("found ").appendValue(count(item));
}
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("containing <" + count + "> occurrence of ").appendValue(this.substring);
+ }
+
private int count(String string) {
- return TestUtils.count(string, substring);
+ return TestUtils.count(string, this.substring);
}
}
diff --git a/jadx-core/src/test/java/jadx/tests/integration/SimplifyVisitorStringBuilderTest.java b/jadx-core/src/test/java/jadx/tests/integration/SimplifyVisitorStringBuilderTest.java
new file mode 100644
index 000000000..5baf911d4
--- /dev/null
+++ b/jadx-core/src/test/java/jadx/tests/integration/SimplifyVisitorStringBuilderTest.java
@@ -0,0 +1,94 @@
+package jadx.tests.integration;
+
+import jadx.core.dex.nodes.ClassNode;
+import jadx.core.dex.visitors.SimplifyVisitor;
+import jadx.core.utils.exceptions.JadxException;
+import jadx.tests.api.IntegrationTest;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Test the StringBuilder simplification part of {@link SimplifyVisitor}
+ *
+ * @author Jan Peter Stotz
+ */
+public class SimplifyVisitorStringBuilderTest extends IntegrationTest {
+
+ public static class TestCls1 {
+ public String test() {
+ return new StringBuilder("[init]").append("a1").append('c').append(2).append(0l).append(1.0f).
+ append(2.0d).append(true).toString();
+ }
+ }
+
+ @Test
+ public void test1() throws JadxException {
+ ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestCls1.class);
+ SimplifyVisitor visitor = new SimplifyVisitor();
+ visitor.visit(cls);
+ String code = cls.getCode().toString();
+ assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 2 + 0 + 1.0f + 2.0d + true;"));
+ }
+
+ public static class TestCls2 {
+ public String test() {
+ // A chain with non-final variables
+ String sInit = "[init]";
+ String s = "a1";
+ char c = 'c';
+ int i = 1;
+ long l = 2;
+ float f = 1.0f;
+ double d = 2.0d;
+ boolean b = true;
+ return new StringBuilder(sInit).append(s).append(c).append(i).append(l).append(f).
+ append(d).append(b).toString();
+ }
+ }
+
+ @Test
+ public void test2() throws JadxException {
+ ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestCls2.class);
+ SimplifyVisitor visitor = new SimplifyVisitor();
+ visitor.visit(cls);
+ String code = cls.getCode().toString();
+ assertThat(code, containsString("return \"[init]\" + \"a1\" + 'c' + 1 + 2 + 1.0f + 2.0d + true;"));
+ }
+
+ public static class TestClsStringUtilsReverse {
+
+ /**
+ * Simplified version of org.apache.commons.lang3.StringUtils.reverse()
+ */
+ public static String reverse(final String str) {
+ return new StringBuilder(str).reverse().toString();
+ }
+ }
+
+ @Test
+ public void test3() throws JadxException {
+ ClassNode cls = getClassNode(SimplifyVisitorStringBuilderTest.TestClsStringUtilsReverse.class);
+ SimplifyVisitor visitor = new SimplifyVisitor();
+ visitor.visit(cls);
+ String code = cls.getCode().toString();
+ assertThat(code, containsString("return new StringBuilder(str).reverse().toString();"));
+ }
+
+ public static class TestClsChainWithDelete {
+ public String test() {
+ // a chain we can't simplify
+ return new StringBuilder("[init]").append("a1").delete(1, 2).toString();
+ }
+ }
+
+ @Test
+ public void testChainWithDelete() throws JadxException {
+ ClassNode cls = getClassNode(TestClsChainWithDelete.class);
+ SimplifyVisitor visitor = new SimplifyVisitor();
+ visitor.visit(cls);
+ String code = cls.getCode().toString();
+ assertThat(code, containsString("return new StringBuilder(\"[init]\").append(\"a1\").delete(1, 2).toString();"));
+ }
+}
diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle
index 956922536..65b3dd065 100644
--- a/jadx-gui/build.gradle
+++ b/jadx-gui/build.gradle
@@ -10,15 +10,15 @@ mainClassName = 'jadx.gui.JadxGUI'
dependencies {
compile(project(":jadx-core"))
compile(project(":jadx-cli"))
- compile 'com.fifesoft:rsyntaxtextarea:2.6.1'
+ compile 'com.fifesoft:rsyntaxtextarea:3.0.0'
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'
- compile 'org.apache.commons:commons-lang3:3.7'
+ compile 'org.apache.commons:commons-lang3:3.8.1'
- compile 'io.reactivex.rxjava2:rxjava:2.1.17'
- compile "com.github.akarnokd:rxjava2-swing:0.2.16"
+ compile 'io.reactivex.rxjava2:rxjava:2.2.5'
+ compile "com.github.akarnokd:rxjava2-swing:0.3.3"
}
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 5e79fc301..f11a0bcf1 100644
--- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
+++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
@@ -5,6 +5,7 @@ import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,10 +65,34 @@ public class JadxWrapper {
new Thread(save).start();
}
+ /**
+ * Get the complete list of classes
+ * @return
+ */
public List getClasses() {
return decompiler.getClasses();
}
+ /**
+ * Get all classes that are not excluded by the excluded packages settings
+ * @return
+ */
+ public List getIncludedClasses() {
+ List classList = decompiler.getClasses();
+ String excludedPackages = settings.getExcludedPackages().trim();
+ if (excludedPackages.length() == 0)
+ return classList;
+ String[] excluded = excludedPackages.split("[ ]+");
+
+ return classList.stream().filter(cls -> {
+ for (String exclude : excluded) {
+ if (cls.getFullName().startsWith(exclude))
+ return false;
+ }
+ return true;
+ }).collect(Collectors.toList());
+ }
+
public List getPackages() {
return decompiler.getPackages();
}
@@ -79,4 +104,8 @@ public class JadxWrapper {
public File getOpenFile() {
return openFile;
}
+
+ public JadxSettings getSettings() {
+ return settings;
+ }
}
diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java
index f457b87fc..cd86c3747 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundJob.java
@@ -44,7 +44,7 @@ public abstract class BackgroundJob {
public Boolean call() throws Exception {
runJob();
executor.shutdown();
- return executor.awaitTermination(5, TimeUnit.MINUTES);
+ return executor.awaitTermination(5, TimeUnit.DAYS);
}
});
}
diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
index 00e13f951..59ad04e39 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
@@ -3,6 +3,7 @@ package jadx.gui.jobs;
import javax.swing.*;
import java.util.concurrent.Future;
+import jadx.gui.utils.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,6 +63,9 @@ public class BackgroundWorker extends SwingWorker {
if (searchIndex != null && searchIndex.getSkippedCount() > 0) {
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
searchIndex.getSkippedCount(), Utils.memoryInfo());
+ String msg = NLS.str("message.indexingClassesSkipped");
+ msg = String.format(msg, searchIndex.getSkippedCount());
+ JOptionPane.showMessageDialog(null, msg);
}
} catch (Exception e) {
LOG.error("Exception in background worker", e);
diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
index df4af302f..983165067 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
@@ -10,7 +10,7 @@ public class DecompileJob extends BackgroundJob {
}
protected void runJob() {
- for (final JavaClass cls : wrapper.getClasses()) {
+ for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(new Runnable() {
@Override
public void run() {
diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
index 2d94456ab..ed1da813f 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
@@ -33,7 +33,7 @@ public class IndexJob extends BackgroundJob {
final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
- for (final JavaClass cls : wrapper.getClasses()) {
+ for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(new Runnable() {
@Override
public void run() {
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 56e1ba0d8..c88a6ba3e 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
@@ -42,6 +42,9 @@ public class JadxSettings extends JadxCLIArgs {
private String editorThemePath = "";
private LangLocale langLocale = NLS.defaultLocale();
private boolean autoStartJobs = false;
+ protected String excludedPackages = "";
+
+ private boolean showHeapUsageBar = true;
private int settingsVersion = 0;
@@ -147,6 +150,24 @@ public class JadxSettings extends JadxCLIArgs {
return true;
}
+
+ public boolean isShowHeapUsageBar() {
+ return showHeapUsageBar;
+ }
+
+ public void setShowHeapUsageBar(boolean showHeapUsageBar) {
+ this.showHeapUsageBar = showHeapUsageBar;
+ partialSync(settings -> settings.showHeapUsageBar = showHeapUsageBar);
+ }
+
+ public String getExcludedPackages() {
+ return excludedPackages;
+ }
+
+ public void setExcludedPackages(String excludedPackages) {
+ this.excludedPackages = excludedPackages;
+ }
+
public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
}
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 af2bd3f13..e61027f74 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
@@ -1,6 +1,8 @@
package jadx.gui.settings;
import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
@@ -242,6 +244,16 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
+ JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
+ editExcludedPackages.addActionListener( event -> {
+
+ String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
+ settings.getExcludedPackages());
+ if (result !=null) {
+ settings.setExcludedPackages(result);
+ }
+ });
+
JCheckBox autoStartJobs = new JCheckBox();
autoStartJobs.setSelected(settings.isAutoStartJobs());
autoStartJobs.addItemListener(e -> settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED));
@@ -269,6 +281,8 @@ public class JadxSettingsWindow extends JDialog {
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount);
+ other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"),
+ editExcludedPackages);
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode);
other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode);
@@ -334,6 +348,10 @@ public class JadxSettingsWindow extends JDialog {
}
public void addRow(String label, JComponent comp) {
+ addRow(label, null, comp);
+ }
+
+ public void addRow(String label, String tooltip, JComponent comp) {
c.gridy = row++;
JLabel jLabel = new JLabel(label);
jLabel.setLabelFor(comp);
@@ -349,6 +367,12 @@ public class JadxSettingsWindow extends JDialog {
c.anchor = GridBagConstraints.CENTER;
c.weightx = 0.2;
c.fill = GridBagConstraints.HORIZONTAL;
+
+ if (tooltip != null) {
+ jLabel.setToolTipText(tooltip);
+ comp.setToolTipText(tooltip);
+ }
+
add(comp, c);
comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue()));
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/HeapUsageBar.java b/jadx-gui/src/main/java/jadx/gui/ui/HeapUsageBar.java
new file mode 100644
index 000000000..29abbdb45
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/ui/HeapUsageBar.java
@@ -0,0 +1,68 @@
+package jadx.gui.ui;
+
+import jadx.gui.utils.NLS;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class HeapUsageBar extends JProgressBar implements ActionListener {
+
+ private static final double TWO_TO_20 = 1048576d; // 1024 * 1024
+
+ private final Color GREEN = new Color(0, 180, 0);
+ private final Color RED = new Color(200, 0, 0);
+
+ private final Runtime r;
+
+ private String maxHeapStr;
+
+ private final Timer timer;
+
+ private final double maxGB;
+
+ private final String textFormat;
+
+ public HeapUsageBar() {
+ super();
+ textFormat = NLS.str("heapUsage.text");
+ r = Runtime.getRuntime();
+ setBorderPainted(false);
+ setStringPainted(true);
+ setValue(10);
+ int maxKB = (int) (r.maxMemory() / 1024);
+ setMaximum(maxKB);
+ maxGB = maxKB / TWO_TO_20;
+ update();
+ timer = new Timer(1000, this);
+ }
+
+ public void update() {
+ long used = r.totalMemory() - r.freeMemory();
+ int usedKB = (int) (used / 1024);
+ setValue(usedKB);
+ setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB));
+
+ if (used > r.totalMemory() * 0.8) {
+ setForeground(RED);
+ } else {
+ setForeground(GREEN);
+ }
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ update();
+ }
+
+ @Override
+ public void setVisible(boolean aFlag) {
+ super.setVisible(aFlag);
+ if (aFlag) {
+ timer.start();
+ } else {
+ timer.stop();
+ }
+ }
+}
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 5b8bf67c8..7315cc828 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -90,10 +90,12 @@ public class MainWindow extends JFrame {
private DefaultTreeModel treeModel;
private JRoot treeRoot;
private TabbedPane tabbedPane;
+ private HeapUsageBar heapUsageBar;
private boolean isFlattenPackage;
private JToggleButton flatPkgButton;
private JCheckBoxMenuItem flatPkgMenuItem;
+ private JCheckBoxMenuItem heapUsageBarMenuItem;
private JToggleButton deobfToggleBtn;
private JCheckBoxMenuItem deobfMenuItem;
@@ -124,6 +126,7 @@ public class MainWindow extends JFrame {
public void open() {
pack();
setLocationAndPosition();
+ heapUsageBar.setVisible(settings.isShowHeapUsageBar());
setVisible(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
@@ -185,7 +188,7 @@ public class MainWindow extends JFrame {
protected void resetCache() {
cacheObject.reset();
// TODO: decompilation freezes sometime with several threads
- int threadsCount = 1; // settings.getThreadsCount();
+ int threadsCount = settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
}
@@ -385,6 +388,13 @@ public class MainWindow extends JFrame {
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
flatPkgMenuItem.setState(isFlattenPackage);
+ heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
+ heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar());
+ heapUsageBarMenuItem.addActionListener(event -> {
+ settings.setShowHeapUsageBar(!settings.isShowHeapUsageBar());
+ heapUsageBar.setVisible(settings.isShowHeapUsageBar());
+ });
+
Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) {
@Override
public void actionPerformed(ActionEvent e) {
@@ -483,6 +493,7 @@ public class MainWindow extends JFrame {
view.setMnemonic(KeyEvent.VK_V);
view.add(flatPkgMenuItem);
view.add(syncAction);
+ view.add(heapUsageBarMenuItem);
JMenu nav = new JMenu(NLS.str("menu.navigation"));
nav.setMnemonic(KeyEvent.VK_N);
@@ -609,6 +620,9 @@ public class MainWindow extends JFrame {
new DropTarget(this, DnDConstants.ACTION_COPY, new MainDropTarget(this));
+ heapUsageBar = new HeapUsageBar();
+ mainPanel.add(heapUsageBar, BorderLayout.SOUTH);
+
setContentPane(mainPanel);
setTitle(DEFAULT_TITLE);
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
index 5cc811299..62c9dad58 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
@@ -180,6 +180,7 @@ public class SearchDialog extends CommonSearchDialog {
.toList()
.toFlowable(), 1)
.observeOn(SwingSchedulers.edt())
+ .doOnError(e -> LOG.error("Error while searching: {}", e.getMessage(), e))
.subscribe(this::processSearchResults);
}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java
index 2c7328972..6aaf8e6e2 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java
@@ -23,7 +23,7 @@ public class CodeIndex implements SearchIndex {
}
@Override
- public void put(StringRef str, T value) {
+ public synchronized void put(StringRef str, T value) {
if (str == null || str.length() == 0) {
return;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java
index 8e577f6da..92a7fbdfb 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java
@@ -69,15 +69,17 @@ public class TextSearchIndex {
int count = lines.size();
for (int i = 0; i < count; i++) {
StringRef line = lines.get(i);
- if (line.length() != 0 && line.charAt(0) != '}') {
- int lineNum = i + 1;
- JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
- CodeNode codeNode = new CodeNode(nodeCache.makeFrom(node == null ? cls : node), lineNum, line);
- if (strRefSupported) {
- codeIndex.put(line, codeNode);
- } else {
- codeIndex.put(line.toString(), codeNode);
- }
+ int lineLength = line.length();
+ if (lineLength == 0 || (lineLength == 1 && line.charAt(0) == '}')) {
+ continue;
+ }
+ int lineNum = i + 1;
+ JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
+ CodeNode codeNode = new CodeNode(nodeCache.makeFrom(node == null ? cls : node), lineNum, line);
+ if (strRefSupported) {
+ codeIndex.put(line, codeNode);
+ } else {
+ codeIndex.put(line.toString(), codeNode);
}
}
} catch (Exception e) {
diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
index a0705904d..839443e44 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -7,6 +7,7 @@ menu.no_recent_files=No recent files
menu.preferences=Preferences
menu.sync=Sync with editor
menu.flatten=Show flatten packages
+menu.heapUsageBar=Show memory usage bar
menu.navigation=Navigation
menu.text_search=Text search
menu.class_search=Class search
@@ -46,6 +47,10 @@ tabs.closeAll=Close All
nav.back=Back
nav.forward=Forward
+message.indexingClassesSkipped=Jadx is running low on memory. Therefore %d classes were not indexed.
If you want all classes to be indexed restart Jadx with increased maximum heap size.
+
+heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
+
search_dialog.open=Open
search_dialog.cancel=Cancel
search_dialog.open_by_name=Search for text:
@@ -84,6 +89,10 @@ preferences.replaceConsts=Replace constants
preferences.useImports=Use import statements
preferences.skipResourcesDecode=Don't decode resources
preferences.threads=Processing threads count
+preferences.excludedPackages=Excluded packages
+preferences.excludedPackages.tooltip=List of space separated package names that will not be decompiled or indexed (saves RAM)
+preferences.excludedPackages.button=Edit
+preferences.excludedPackages.editDialog=List of space separated package names that will not be decompiled or indexed (saves RAM)
e.g. android.support
preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font