Compare commits

...

25 Commits

Author SHA1 Message Date
Skylot 6844a46c93 fix: disable HTML rendering in labels if not needed 2022-10-20 15:58:23 +01:00
Skylot e9e45707da chore: update dependencies 2022-10-20 14:54:31 +01:00
Skylot b9d02ff4c4 refactor: remove all LinkedList usage 2022-10-12 17:05:08 +01:00
Jan S 29b64300bc fix(gui): multi-threading issue in DebugController fixed (#1701) (PR #1702) 2022-10-11 19:21:06 +01:00
zhongqingsong 777355e86e fix(gui): update Messages_zh_CN.properties (PR #1700)
Add new text about logcat.
2022-10-10 18:57:58 +01:00
Skylot 620a177ce8 fix: restore enum class with custom code in static init (#1699) 2022-10-08 21:54:06 +01:00
Skylot 683c2dfbeb fix: improve ternary inline, resolve more enum cases (#1686) 2022-10-07 15:51:11 +01:00
Skylot 266cbcc6f4 fix(gui): migrate to fixed jdwp library fork (#1471) 2022-10-06 19:47:15 +01:00
Jan S 8a45602ae6 fix: improve logging messages for zip security errors (#750)(PR #1698)
Logging error messages on invalid file-names or path traversal attacks improved
2022-10-06 19:31:42 +01:00
Skylot 711419a797 fix: correct fix for all use places of incompatible primitives (#1688) 2022-10-03 00:11:04 +03:00
Skylot 603f3057eb chore: update dependencies 2022-10-03 00:11:01 +03:00
5idereal fa6fc1f871 fix(gui): update zh-TW translations (PR #1694) 2022-10-02 15:50:11 +01:00
Skylot 49fa320989 fix: handle possible concurrent exception in method codegen (#1685) 2022-09-29 20:28:01 +01:00
Skylot 2f301bf150 fix: don't mark constructor for inline if anonymous class inline is disabled (#1680) 2022-09-25 17:47:53 +01:00
Skylot b4892ce17f build: use fixed java version to build win artifacts 2022-09-23 21:08:16 +01:00
Skylot 151c171616 fix: handle empty block at end of else-if chain (#1674) 2022-09-23 20:40:56 +01:00
Skylot 79477a2de3 fix: don't rename bridged overridden methods (#1672) 2022-09-23 19:16:34 +01:00
dependabot[bot] 78aadda931 build(deps): bump actions/checkout from 2 to 3 (PR #1669)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 18:30:35 +01:00
Jan S b50706505f fix(res): implemented parsing RES_TABLE_TYPE_LIBRARY chunks (#1663)(PR #1664)
* core: Implemented parsing RES_TABLE_TYPE_LIBRARY chunks

* skip unknown data at the end of type chunk
2022-09-10 16:58:26 +01:00
The Cobra Chicken 9114821fb1 feat(debugger): add logcat output (#1411)(PR #1666)
* Adding logcatController class and writing adb / debugger panel information to the controller.

* Finished parsing logcat binary output and writing an arraylist containing all events.

* added highlighting of logcat output based on type.  Added timestamp parsing.

* Updated code to only get new log messages.

* Added additional code for select all

* Completed Check and uncheckall options.

* Changed log highlighting to log color.  Changed from JTextArea to JTextPane. Logcat pane will now autoscroll only if it is already scrolled to the bottom.  Debugger exit will now stop logcat as well.

* Moved labels into NLS rather than using hardcoded strings.

* Implemented the ability to autoselect attached process.  Changed the formatting of logcat messages.

* Moved labels into NLS rather than using hardcoded strings.

* updating to use info getter methods rather than directly accessing variable

* Added Logcat Pause Button

* Added Clear button

* Updated clear icon

* Cleaning warnings

* cleaning

* Changed behavior to only show logcat for debugged process to start with.

* cleaning

* cleaning

* cleaning

* applying spotless

* Fixing bug with switch

* fixed formatting issue

* add missing localization strings

Co-authored-by: green9317 <38409554+green9317@users.noreply.github.com>
Co-authored-by: TheCobraChicken <jeffmlitfi@gmail.com>
Co-authored-by: Skylot <skylot@gmail.com>
2022-09-08 15:18:55 +01:00
Skylot 1195582da8 feat(gui): option for search results count per page (#1652) 2022-09-05 20:07:02 +01:00
Skylot 258987b0ff chore: update dependencies 2022-09-05 20:07:01 +01:00
Guilherme a6a734c70d fix(gui): update pt-BR translation (PR #1655) 2022-09-01 15:51:12 +01:00
Choiman1559 d6c23a2a9b fix(gui): update Korean translation (PR #1650)
* update korean
* Update Messages_ko_KR.properties
* Restore missing empty line

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
2022-08-20 17:59:37 +01:00
Skylot db028904d7 fix(gui): set legacy sort flag also for launch4j (#1628) 2022-08-20 17:37:26 +01:00
76 changed files with 2037 additions and 579 deletions
+3 -1
View File
@@ -57,7 +57,9 @@ jobs:
fetch-depth: 0
- name: Set up JDK
uses: oracle-actions/setup-java@v1 # set latest java version by default
uses: oracle-actions/setup-java@v1
with:
release: 18
- name: Print Java version
shell: bash
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
@@ -6,5 +6,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: gradle/wrapper-validation-action@v1
+7 -7
View File
@@ -1,6 +1,6 @@
plugins {
id 'com.github.ben-manes.versions' version '0.42.0'
id 'com.diffplug.spotless' version '6.9.1'
id 'com.github.ben-manes.versions' version '0.43.0'
id 'com.diffplug.spotless' version '6.11.0'
}
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -26,16 +26,16 @@ allprojects {
}
dependencies {
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'org.slf4j:slf4j-api:2.0.3'
compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.11'
testImplementation 'ch.qos.logback:logback-classic:1.3.4'
testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.7.0'
testImplementation 'org.mockito:mockito-core:4.8.0'
testImplementation 'org.assertj:assertj-core:3.23.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
testCompileOnly 'org.jetbrains:annotations:23.0.0'
}
+1 -1
View File
@@ -11,7 +11,7 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.11'
implementation 'ch.qos.logback:logback-classic:1.3.4'
}
application {
+3 -3
View File
@@ -8,8 +8,8 @@ dependencies {
implementation 'com.google.code.gson:gson:2.9.1'
// TODO: move resources decoding to separate plugin module
implementation 'com.android.tools.build:aapt2-proto:7.2.2-7984345'
implementation 'com.google.protobuf:protobuf-java:3.21.5' // forcing latest version
implementation 'com.android.tools.build:aapt2-proto:7.3.1-8691043'
implementation 'com.google.protobuf:protobuf-java:3.21.8' // forcing latest version
testImplementation 'org.apache.commons:commons-lang3:3.12.0'
@@ -19,7 +19,7 @@ dependencies {
testRuntimeOnly(project(':jadx-plugins:jadx-java-input'))
testRuntimeOnly(project(':jadx-plugins:jadx-raung-input'))
testImplementation 'org.eclipse.jdt:ecj:3.30.0'
testImplementation 'org.eclipse.jdt:ecj:3.31.0'
testImplementation 'tools.profiler:async-profiler:1.8.3'
}
@@ -171,7 +171,7 @@ public class ClassGen {
ArgType sup = cls.getSuperClass();
if (sup != null
&& !sup.equals(ArgType.OBJECT)
&& !cls.isEnum()) {
&& !cls.contains(AFlag.REMOVE_SUPER_CLASS)) {
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
@@ -322,19 +322,25 @@ public class ClassGen {
if (inlineAttr == null || inlineAttr.notNeeded()) {
return false;
}
if (mth.getUseIn().isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
try {
if (mth.getUseIn().isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
List<MethodNode> useInCompleted = mth.getUseIn().stream()
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
.collect(Collectors.toList());
if (useInCompleted.isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
return false;
} catch (Exception e) {
// check failed => keep method
mth.addWarnComment("Failed to check method usage", e);
return false;
}
List<MethodNode> useInCompleted = mth.getUseIn().stream()
.filter(m -> m.getTopParentClass().getState().isProcessComplete())
.collect(Collectors.toList());
if (useInCompleted.isEmpty()) {
mth.add(AFlag.DONT_GENERATE);
return true;
}
mth.addDebugComment("Method not inlined, still used in: " + useInCompleted);
return false;
}
private boolean isMethodsPresents() {
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import jadx.api.ICodeWriter;
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class ConditionGen extends InsnGen {
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<>();
private final Queue<IfCondition> stack = new ArrayDeque<>();
public Queue<IfCondition> getStack() {
return stack;
@@ -21,6 +21,7 @@ public enum AFlag {
DONT_GENERATE, // process as usual, but don't output to generated code
COMMENT_OUT, // process as usual, but comment insn in generated code
REMOVE, // can be completely removed
REMOVE_SUPER_CLASS, // don't add super class
HIDDEN, // instruction used inside other instruction but not listed in args
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.nodes;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
@@ -10,7 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
public class PhiListAttr implements IJadxAttribute {
private final List<PhiInsn> list = new LinkedList<>();
private final List<PhiInsn> list = new ArrayList<>();
@Override
public AType<PhiListAttr> getAttrType() {
@@ -805,7 +805,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
public void addCodegenDep(ClassNode dep) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
if (!codegenDeps.contains(dep)) {
this.codegenDeps = ListUtils.safeAdd(this.codegenDeps, dep);
}
}
public int getTotalDepsCount() {
@@ -156,7 +156,7 @@ public final class IfCondition extends AttrNode {
return i;
}
if (c.getOp() == IfOp.EQ && c.getB().isFalse()) {
cond = not(new IfCondition(c.invert()));
cond = new IfCondition(Mode.NOT, Collections.singletonList(new IfCondition(c.invert())));
} else {
c.normalize();
}
@@ -62,45 +62,47 @@ public class ConstInlineVisitor extends AbstractVisitor {
|| insn.getResult() == null) {
return;
}
SSAVar sVar = insn.getResult().getSVar();
InsnArg constArg;
Runnable onSuccess = null;
InsnType insnType = insn.getType();
if (insnType == InsnType.CONST || insnType == InsnType.MOVE) {
constArg = insn.getArg(0);
if (!constArg.isLiteral()) {
switch (insn.getType()) {
case CONST:
case MOVE: {
constArg = insn.getArg(0);
if (!constArg.isLiteral()) {
return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
break;
}
case CONST_STR: {
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
} else {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
}
break;
}
case CONST_CLASS: {
if (sVar.isUsedInPhi()) {
return;
}
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
constArg.setType(ArgType.CLASS);
break;
}
default:
return;
}
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
} else if (insnType == InsnType.CONST_STR) {
if (sVar.isUsedInPhi()) {
return;
}
String s = ((ConstStringNode) insn).getString();
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
} else {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> f.addUseIn(mth);
}
} else if (insnType == InsnType.CONST_CLASS) {
if (sVar.isUsedInPhi()) {
return;
}
constArg = InsnArg.wrapArg(insn.copyWithoutResult());
constArg.setType(ArgType.CLASS);
} else {
return;
}
// all check passed, run replace
@@ -37,9 +37,13 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.BlockInsnPair;
import jadx.core.utils.BlockUtils;
@@ -47,6 +51,7 @@ import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.InsnUtils.checkInsnType;
import static jadx.core.utils.InsnUtils.getSingleArg;
@@ -55,8 +60,16 @@ import static jadx.core.utils.InsnUtils.getWrappedInsn;
@JadxVisitor(
name = "EnumVisitor",
desc = "Restore enum classes",
runAfter = { CodeShrinkVisitor.class, ModVisitor.class, ReSugarCode.class },
runBefore = { ExtractFieldInit.class }
runAfter = {
CodeShrinkVisitor.class, // all possible instructions already inlined
ModVisitor.class,
ReSugarCode.class,
IfRegionVisitor.class, // ternary operator inlined
CheckRegions.class // regions processing finished
},
runBefore = {
ExtractFieldInit.class
}
)
public class EnumVisitor extends AbstractVisitor {
@@ -81,82 +94,67 @@ public class EnumVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
boolean converted;
try {
converted = convertToEnum(cls);
} catch (Exception e) {
cls.addWarnComment("Enum visitor error", e);
converted = false;
}
if (!converted) {
AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed");
if (cls.isEnum()) {
boolean converted;
try {
converted = convertToEnum(cls);
} catch (Exception e) {
cls.addWarnComment("Enum visitor error", e);
converted = false;
}
if (!converted) {
AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
cls.addWarnComment("Failed to restore enum class, 'enum' modifier and super class removed");
}
}
}
return true;
}
private boolean convertToEnum(ClassNode cls) {
if (!cls.isEnum()) {
return false;
ArgType superType = cls.getSuperClass();
if (superType != null && superType.getObject().equals(ArgType.ENUM.getObject())) {
cls.add(AFlag.REMOVE_SUPER_CLASS);
}
MethodNode classInitMth = cls.getClassInitMth();
if (classInitMth == null) {
cls.addWarnComment("Enum class init method not found");
return false;
}
if (classInitMth.getBasicBlocks().isEmpty()) {
Region staticRegion = classInitMth.getRegion();
if (staticRegion == null || classInitMth.getBasicBlocks().isEmpty()) {
return false;
}
ArgType clsType = cls.getClassInfo().getType();
// search "$VALUES" field (holds all enum values)
List<FieldNode> valuesCandidates = cls.getFields().stream()
.filter(f -> f.getAccessFlags().isStatic())
.filter(f -> f.getType().isArray())
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
.collect(Collectors.toList());
if (valuesCandidates.isEmpty()) {
return false;
}
if (valuesCandidates.size() > 1) {
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
}
if (valuesCandidates.size() > 1) {
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
if (valuesOpt.isPresent()) {
valuesCandidates.clear();
valuesCandidates.add(valuesOpt.get());
// collect blocks on linear part of static method (ignore branching on method end)
List<BlockNode> staticBlocks = new ArrayList<>();
for (IContainer subBlock : staticRegion.getSubBlocks()) {
if (subBlock instanceof BlockNode) {
staticBlocks.add((BlockNode) subBlock);
} else {
break;
}
}
if (valuesCandidates.size() != 1) {
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
if (staticBlocks.isEmpty()) {
cls.addWarnComment("Unexpected branching in enum static init block");
return false;
}
FieldNode valuesField = valuesCandidates.get(0);
List<InsnNode> toRemove = new ArrayList<>();
// search "$VALUES" array init and collect enum fields
BlockInsnPair valuesInitPair = getValuesInitInsn(classInitMth, valuesField);
if (valuesInitPair == null) {
EnumData data = new EnumData(cls, classInitMth, staticBlocks);
if (!searchValuesField(data)) {
return false;
}
BlockNode staticBlock = valuesInitPair.getBlock();
InsnNode valuesInitInsn = valuesInitPair.getInsn();
List<EnumField> enumFields = null;
InsnArg arrArg = valuesInitInsn.getArg(0);
InsnArg arrArg = data.valuesInitInsn.getArg(0);
if (arrArg.isInsnWrap()) {
InsnNode wrappedInsn = ((InsnWrapArg) arrArg).getWrapInsn();
enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
enumFields = extractEnumFieldsFromInsn(data, wrappedInsn);
}
if (enumFields == null) {
cls.addWarnComment("Unknown enum class pattern. Please report as an issue!");
return false;
}
toRemove.add(valuesInitInsn);
data.toRemove.add(data.valuesInitInsn);
// all checks complete, perform transform
EnumClassAttr attr = new EnumClassAttr(enumFields);
@@ -176,59 +174,92 @@ public class EnumVisitor extends AbstractVisitor {
fieldNode.getFieldInfo().setAlias(name);
}
fieldNode.add(AFlag.DONT_GENERATE);
processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove);
processConstructorInsn(data, enumField, classInitMth);
}
valuesField.add(AFlag.DONT_GENERATE);
InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove);
data.valuesField.add(AFlag.DONT_GENERATE);
InsnRemover.removeAllAndUnbind(classInitMth, data.toRemove);
if (classInitMth.countInsns() == 0) {
classInitMth.add(AFlag.DONT_GENERATE);
} else if (!toRemove.isEmpty()) {
} else if (!data.toRemove.isEmpty()) {
CodeShrinkVisitor.shrinkMethod(classInitMth);
}
removeEnumMethods(cls, clsType, valuesField);
removeEnumMethods(cls, data.valuesField);
return true;
}
private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth,
BlockNode staticBlock, List<InsnNode> toRemove) {
ConstructorInsn co = enumField.getConstrInsn();
ClassInfo enumClsInfo = co.getClassType();
if (!enumClsInfo.equals(cls.getClassInfo())) {
ClassNode enumCls = cls.root().resolveClass(enumClsInfo);
if (enumCls != null) {
processEnumCls(cls, enumField, enumCls);
/**
* Search "$VALUES" field (holds all enum values)
*/
private boolean searchValuesField(EnumData data) {
ArgType clsType = data.cls.getClassInfo().getType();
List<FieldNode> valuesCandidates = data.cls.getFields().stream()
.filter(f -> f.getAccessFlags().isStatic())
.filter(f -> f.getType().isArray())
.filter(f -> Objects.equals(f.getType().getArrayRootElement(), clsType))
.collect(Collectors.toList());
if (valuesCandidates.isEmpty()) {
data.cls.addWarnComment("$VALUES field not found");
return false;
}
if (valuesCandidates.size() > 1) {
valuesCandidates.removeIf(f -> !f.getAccessFlags().isSynthetic());
}
if (valuesCandidates.size() > 1) {
Optional<FieldNode> valuesOpt = valuesCandidates.stream().filter(f -> f.getName().equals("$VALUES")).findAny();
if (valuesOpt.isPresent()) {
valuesCandidates.clear();
valuesCandidates.add(valuesOpt.get());
}
}
List<RegisterArg> regs = new ArrayList<>();
co.getRegisterArgs(regs);
if (!regs.isEmpty()) {
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
if (valuesCandidates.size() != 1) {
data.cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
return false;
}
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
data.valuesField = valuesCandidates.get(0);
// search "$VALUES" array init and collect enum fields
BlockInsnPair valuesInitPair = getValuesInitInsn(data);
if (valuesInitPair == null) {
return false;
}
data.valuesInitInsn = valuesInitPair.getInsn();
return true;
}
private void processConstructorInsn(EnumData data, EnumField enumField, MethodNode classInitMth) {
ConstructorInsn co = enumField.getConstrInsn();
ClassInfo enumClsInfo = co.getClassType();
if (!enumClsInfo.equals(data.cls.getClassInfo())) {
ClassNode enumCls = data.cls.root().resolveClass(enumClsInfo);
if (enumCls != null) {
processEnumCls(data.cls, enumField, enumCls);
}
}
MethodNode ctrMth = data.cls.root().resolveMethod(co.getCallMth());
if (ctrMth != null) {
markArgsForSkip(ctrMth);
}
RegisterArg coResArg = co.getResult();
if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) {
toRemove.add(co);
data.toRemove.add(co);
} else {
// constructor result used in other places -> replace constructor with enum field get (SGET)
IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0);
enumGet.setResult(coResArg.duplicate());
BlockUtils.replaceInsn(classInitMth, staticBlock, co, enumGet);
BlockUtils.replaceInsn(classInitMth, co, enumGet);
}
}
@Nullable
private List<EnumField> extractEnumFieldsFromInsn(ClassNode cls, BlockNode staticBlock,
InsnNode wrappedInsn, List<InsnNode> toRemove) {
private List<EnumField> extractEnumFieldsFromInsn(EnumData enumData, InsnNode wrappedInsn) {
switch (wrappedInsn.getType()) {
case FILLED_NEW_ARRAY:
return extractEnumFieldsFromFilledArray(cls, wrappedInsn, staticBlock, toRemove);
return extractEnumFieldsFromFilledArray(enumData, wrappedInsn);
case INVOKE:
// handle redirection of values array fill (added in java 15)
return extractEnumFieldsFromInvoke(cls, staticBlock, (InvokeNode) wrappedInsn, toRemove);
return extractEnumFieldsFromInvoke(enumData, (InvokeNode) wrappedInsn);
case NEW_ARRAY:
InsnArg arg = wrappedInsn.getArg(0);
@@ -243,10 +274,9 @@ public class EnumVisitor extends AbstractVisitor {
}
}
private List<EnumField> extractEnumFieldsFromInvoke(ClassNode cls, BlockNode staticBlock,
InvokeNode invokeNode, List<InsnNode> toRemove) {
private List<EnumField> extractEnumFieldsFromInvoke(EnumData enumData, InvokeNode invokeNode) {
MethodInfo callMth = invokeNode.getCallMth();
MethodNode valuesMth = cls.root().resolveMethod(callMth);
MethodNode valuesMth = enumData.cls.root().resolveMethod(callMth);
if (valuesMth == null || valuesMth.isVoidReturn()) {
return null;
}
@@ -256,16 +286,16 @@ public class EnumVisitor extends AbstractVisitor {
if (wrappedInsn == null) {
return null;
}
List<EnumField> enumFields = extractEnumFieldsFromInsn(cls, staticBlock, wrappedInsn, toRemove);
List<EnumField> enumFields = extractEnumFieldsFromInsn(enumData, wrappedInsn);
if (enumFields != null) {
valuesMth.add(AFlag.DONT_GENERATE);
}
return enumFields;
}
private BlockInsnPair getValuesInitInsn(MethodNode classInitMth, FieldNode valuesField) {
FieldInfo searchField = valuesField.getFieldInfo();
for (BlockNode blockNode : classInitMth.getBasicBlocks()) {
private BlockInsnPair getValuesInitInsn(EnumData data) {
FieldInfo searchField = data.valuesField.getFieldInfo();
for (BlockNode blockNode : data.staticBlocks) {
for (InsnNode insn : blockNode.getInstructions()) {
if (insn.getType() == InsnType.SPUT) {
IndexInsnNode indexInsnNode = (IndexInsnNode) insn;
@@ -279,50 +309,49 @@ public class EnumVisitor extends AbstractVisitor {
return null;
}
private List<EnumField> extractEnumFieldsFromFilledArray(ClassNode cls, InsnNode arrFillInsn, BlockNode staticBlock,
List<InsnNode> toRemove) {
private List<EnumField> extractEnumFieldsFromFilledArray(EnumData enumData, InsnNode arrFillInsn) {
List<EnumField> enumFields = new ArrayList<>();
for (InsnArg arg : arrFillInsn.getArguments()) {
EnumField field = null;
if (arg.isInsnWrap()) {
InsnNode wrappedInsn = ((InsnWrapArg) arg).getWrapInsn();
field = processEnumFieldByWrappedInsn(cls, wrappedInsn, staticBlock, toRemove);
field = processEnumFieldByWrappedInsn(enumData, wrappedInsn);
} else if (arg.isRegister()) {
field = processEnumFieldByRegister(cls, (RegisterArg) arg, staticBlock, toRemove);
field = processEnumFieldByRegister(enumData, (RegisterArg) arg);
}
if (field == null) {
return null;
}
enumFields.add(field);
}
toRemove.add(arrFillInsn);
enumData.toRemove.add(arrFillInsn);
return enumFields;
}
private EnumField processEnumFieldByWrappedInsn(ClassNode cls, InsnNode wrappedInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
private EnumField processEnumFieldByWrappedInsn(EnumData data, InsnNode wrappedInsn) {
if (wrappedInsn.getType() == InsnType.SGET) {
return processEnumFieldByField(cls, wrappedInsn, staticBlock, toRemove);
return processEnumFieldByField(data, wrappedInsn);
}
ConstructorInsn constructorInsn = castConstructorInsn(wrappedInsn);
if (constructorInsn != null) {
FieldNode enumFieldNode = createFakeField(cls, "EF" + constructorInsn.getOffset());
cls.addField(enumFieldNode);
return createEnumFieldByConstructor(cls, enumFieldNode, constructorInsn);
FieldNode enumFieldNode = createFakeField(data.cls, "EF" + constructorInsn.getOffset());
data.cls.addField(enumFieldNode);
return createEnumFieldByConstructor(data.cls, enumFieldNode, constructorInsn);
}
return null;
}
@Nullable
private EnumField processEnumFieldByField(ClassNode cls, InsnNode sgetInsn, BlockNode staticBlock, List<InsnNode> toRemove) {
private EnumField processEnumFieldByField(EnumData data, InsnNode sgetInsn) {
if (sgetInsn.getType() != InsnType.SGET) {
return null;
}
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sgetInsn).getIndex();
FieldNode enumFieldNode = cls.searchField(fieldInfo);
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
if (enumFieldNode == null) {
return null;
}
InsnNode sputInsn = searchFieldPutInsn(cls, staticBlock, enumFieldNode);
InsnNode sputInsn = searchFieldPutInsn(data, enumFieldNode);
if (sputInsn == null) {
return null;
}
@@ -333,17 +362,17 @@ public class EnumVisitor extends AbstractVisitor {
}
RegisterArg sgetResult = sgetInsn.getResult();
if (sgetResult == null || sgetResult.getSVar().getUseCount() == 1) {
toRemove.add(sgetInsn);
data.toRemove.add(sgetInsn);
}
toRemove.add(sputInsn);
return createEnumFieldByConstructor(cls, enumFieldNode, co);
data.toRemove.add(sputInsn);
return createEnumFieldByConstructor(data.cls, enumFieldNode, co);
}
@Nullable
private EnumField processEnumFieldByRegister(ClassNode cls, RegisterArg arg, BlockNode staticBlock, List<InsnNode> toRemove) {
private EnumField processEnumFieldByRegister(EnumData data, RegisterArg arg) {
InsnNode assignInsn = arg.getAssignInsn();
if (assignInsn != null && assignInsn.getType() == InsnType.SGET) {
return processEnumFieldByField(cls, assignInsn, staticBlock, toRemove);
return processEnumFieldByField(data, assignInsn);
}
SSAVar ssaVar = arg.getSVar();
@@ -354,12 +383,12 @@ public class EnumVisitor extends AbstractVisitor {
if (constrInsn == null || constrInsn.getType() != InsnType.CONSTRUCTOR) {
return null;
}
FieldNode enumFieldNode = searchEnumField(cls, ssaVar, toRemove);
FieldNode enumFieldNode = searchEnumField(data, ssaVar);
if (enumFieldNode == null) {
enumFieldNode = createFakeField(cls, "EF" + arg.getRegNum());
cls.addField(enumFieldNode);
enumFieldNode = createFakeField(data.cls, "EF" + arg.getRegNum());
data.cls.addField(enumFieldNode);
}
return createEnumFieldByConstructor(cls, enumFieldNode, (ConstructorInsn) constrInsn);
return createEnumFieldByConstructor(data.cls, enumFieldNode, (ConstructorInsn) constrInsn);
}
private FieldNode createFakeField(ClassNode cls, String name) {
@@ -372,17 +401,17 @@ public class EnumVisitor extends AbstractVisitor {
}
@Nullable
private FieldNode searchEnumField(ClassNode cls, SSAVar ssaVar, List<InsnNode> toRemove) {
private FieldNode searchEnumField(EnumData data, SSAVar ssaVar) {
InsnNode sputInsn = ssaVar.getUseList().get(0).getParentInsn();
if (sputInsn == null || sputInsn.getType() != InsnType.SPUT) {
return null;
}
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
FieldNode enumFieldNode = cls.searchField(fieldInfo);
FieldNode enumFieldNode = data.cls.searchField(fieldInfo);
if (enumFieldNode == null) {
return null;
}
toRemove.add(sputInsn);
data.toRemove.add(sputInsn);
return enumFieldNode;
}
@@ -409,24 +438,32 @@ public class EnumVisitor extends AbstractVisitor {
if (ctrMth == null) {
return null;
}
List<RegisterArg> regs = new ArrayList<>();
co.getRegisterArgs(regs);
if (!regs.isEmpty()) {
throw new JadxRuntimeException("Init of enum " + enumFieldNode.getName() + " uses external variables");
}
return new EnumField(enumFieldNode, co);
}
@Nullable
private InsnNode searchFieldPutInsn(ClassNode cls, BlockNode staticBlock, FieldNode enumFieldNode) {
for (InsnNode sputInsn : staticBlock.getInstructions()) {
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
FieldNode fieldNode = cls.searchField(f);
if (Objects.equals(fieldNode, enumFieldNode)) {
return sputInsn;
private InsnNode searchFieldPutInsn(EnumData data, FieldNode enumFieldNode) {
for (BlockNode block : data.staticBlocks) {
for (InsnNode sputInsn : block.getInstructions()) {
if (sputInsn != null && sputInsn.getType() == InsnType.SPUT) {
FieldInfo f = (FieldInfo) ((IndexInsnNode) sputInsn).getIndex();
FieldNode fieldNode = data.cls.searchField(f);
if (Objects.equals(fieldNode, enumFieldNode)) {
return sputInsn;
}
}
}
}
return null;
}
private void removeEnumMethods(ClassNode cls, ArgType clsType, FieldNode valuesField) {
private void removeEnumMethods(ClassNode cls, FieldNode valuesField) {
ArgType clsType = cls.getClassInfo().getType();
String valuesMethodShortId = "values()" + TypeGen.signature(ArgType.array(clsType));
MethodNode valuesMethod = null;
// remove compiler generated methods
@@ -605,4 +642,19 @@ public class EnumVisitor extends AbstractVisitor {
}
return null;
}
private static class EnumData {
final ClassNode cls;
final MethodNode classInitMth;
final List<BlockNode> staticBlocks;
final List<InsnNode> toRemove = new ArrayList<>();
FieldNode valuesField;
InsnNode valuesInitInsn;
public EnumData(ClassNode cls, MethodNode classInitMth, List<BlockNode> staticBlocks) {
this.cls = cls;
this.classInitMth = classInitMth;
this.staticBlocks = staticBlocks;
}
}
}
@@ -1,12 +1,12 @@
package jadx.core.dex.visitors;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -45,7 +45,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
}
AccessInfo accessFlags = mth.getAccessFlags();
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
return isSynthetic && (accessFlags.isStatic() || mth.isConstructor());
return isSynthetic && canInlineMethod(mth, accessFlags);
}
private static boolean canInlineMethod(MethodNode mth, AccessInfo accessFlags) {
if (accessFlags.isStatic()) {
return true;
}
return mth.isConstructor() && mth.root().getArgs().isInlineAnonymousClasses();
}
private static void fixClassDependencies(MethodNode mth) {
@@ -54,8 +61,14 @@ public class ProcessMethodsForInline extends AbstractVisitor {
// remove possible cross dependency
// to force class with inline method to be processed before its usage
ClassNode useTopCls = useInMth.getTopParentClass();
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
useTopCls.addCodegenDep(parentClass);
if (useTopCls != parentClass) {
parentClass.removeDependency(useTopCls);
useTopCls.addCodegenDep(parentClass);
if (Consts.DEBUG_USAGE) {
parentClass.addDebugComment("Remove dependency: " + useTopCls + " to inline " + mth);
useTopCls.addDebugComment("Add dependency: " + parentClass + " to inline " + mth);
}
}
}
}
}
@@ -2,7 +2,6 @@ package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -432,7 +431,7 @@ public class BlockProcessor extends AbstractVisitor {
BlockNode loopHeader = loop.getStart();
List<BlockNode> preds = loopHeader.getPredecessors();
if (preds.size() > 2) {
List<BlockNode> blocks = new LinkedList<>(preds);
List<BlockNode> blocks = new ArrayList<>(preds);
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
BlockNode first = blocks.remove(0);
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
@@ -19,6 +19,10 @@ public class DepthRegionTraversal {
traverseInternal(mth, visitor, mth.getRegion());
}
public static void traverse(MethodNode mth, IContainer container, IRegionVisitor visitor) {
traverseInternal(mth, visitor, container);
}
public static void traverseIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
boolean repeat;
int k = 0;
@@ -3,8 +3,10 @@ package jadx.core.dex.visitors.regions;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.conditions.IfCondition;
@@ -45,7 +47,7 @@ public class IfRegionVisitor extends AbstractVisitor {
}
}
@SuppressWarnings("UnnecessaryReturnStatement")
@SuppressWarnings({ "UnnecessaryReturnStatement", "StatementWithEmptyBody" })
private static void orderBranches(MethodNode mth, IfRegion ifRegion) {
if (RegionUtils.isEmpty(ifRegion.getElseRegion())) {
return;
@@ -79,9 +81,15 @@ public class IfRegionVisitor extends AbstractVisitor {
return;
}
}
boolean lastRegion = ifRegion == RegionUtils.getLastRegion(mth.getRegion());
boolean lastRegion = RegionUtils.hasExitEdge(ifRegion);
if (elseSize == 1 && lastRegion && mth.isVoidReturn()) {
// single return at method end will be removed later
InsnNode lastElseInsn = RegionUtils.getLastInsn(ifRegion.getElseRegion());
if (lastElseInsn != null && lastElseInsn.getType() == InsnType.THROW) {
// move `throw` into `then` block
invertIfRegion(ifRegion);
} else {
// single return at method end will be removed later
}
return;
}
if (!lastRegion) {
@@ -1,6 +1,6 @@
package jadx.core.dex.visitors.regions;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
@@ -125,7 +125,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
return false;
}
// can't make loop if argument from increment instruction is assign in loop
List<RegisterArg> args = new LinkedList<>();
List<RegisterArg> args = new ArrayList<>();
incrInsn.getRegisterArgs(args);
for (RegisterArg iArg : args) {
try {
@@ -268,7 +268,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;")) {
return false;
}
List<InsnNode> toSkip = new LinkedList<>();
List<InsnNode> toSkip = new ArrayList<>();
RegisterArg iterVar;
if (nextCall.contains(AFlag.WRAPPED)) {
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
@@ -73,11 +74,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false;
}
if (elseRegion == null) {
if (mth.isConstructor()) {
// force ternary conversion to inline all code in 'super' or 'this' calls
return processOneBranchTernary(mth, ifRegion);
}
return false;
return processOneBranchTernary(mth, ifRegion);
}
BlockNode tb = getTernaryInsnBlock(thenRegion);
BlockNode eb = getTernaryInsnBlock(elseRegion);
@@ -93,21 +90,8 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
InsnNode thenInsn = tb.getInstructions().get(0);
InsnNode elseInsn = eb.getInstructions().get(0);
if (mth.contains(AFlag.USE_LINES_HINTS)
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
if (!checkLineStats(thenInsn, elseInsn)) {
return false;
}
} else {
// no debug info
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
// don't make nested ternary by default
// TODO: add addition checks
return false;
}
}
if (!verifyLineHints(mth, thenInsn, elseInsn)) {
return false;
}
RegisterArg thenResArg = thenInsn.getResult();
@@ -184,6 +168,20 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false;
}
private static boolean verifyLineHints(MethodNode mth, InsnNode thenInsn, InsnNode elseInsn) {
if (mth.contains(AFlag.USE_LINES_HINTS)
&& thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
// sometimes source lines incorrect
return checkLineStats(thenInsn, elseInsn);
}
// don't make nested ternary by default
// TODO: add addition checks
return !containsTernary(thenInsn) && !containsTernary(elseInsn);
}
return true;
}
private static void clearConditionBlocks(List<BlockNode> conditionBlocks, BlockNode header) {
for (BlockNode block : conditionBlocks) {
if (block != header) {
@@ -277,6 +275,7 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
return false;
}
@SuppressWarnings("StatementWithEmptyBody")
private static void replaceWithTernary(MethodNode mth, IfRegion ifRegion, BlockNode block, InsnNode insn) {
RegisterArg resArg = insn.getResult();
if (resArg.getSVar().getUseList().size() != 1) {
@@ -296,17 +295,45 @@ public class TernaryMod extends AbstractRegionVisitor implements IRegionIterativ
if (otherArg == null) {
return;
}
InsnNode elseAssign = otherArg.getAssignInsn();
if (mth.isConstructor() || (mth.getParentClass().isEnum() && mth.getMethodInfo().isClassInit())) {
// forcing ternary inline for constructors (will help in moving super call to the top) and enums
// skip code style checks
} else {
if (elseAssign != null && elseAssign.isConstInsn()) {
if (!verifyLineHints(mth, insn, elseAssign)) {
return;
}
} else {
if (insn.getResult().sameCodeVar(otherArg)) {
// don't use same variable in else branch to prevent: l = (l == 0) ? 1 : l
return;
}
}
}
// all checks passed
BlockNode header = ifRegion.getConditionBlocks().get(0);
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
return;
}
InsnArg elseArg;
if (elseAssign != null && elseAssign.isConstInsn()) {
// inline constant
SSAVar elseVar = elseAssign.getResult().getSVar();
if (elseVar.getUseCount() == 1 && elseVar.getOnlyOneUseInPhi() == phiInsn) {
InsnRemover.remove(mth, elseAssign);
}
elseArg = InsnArg.wrapInsnIntoArg(elseAssign);
} else {
elseArg = otherArg;
}
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg);
phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), elseArg);
ternInsn.simplifyCondition();
InsnRemover.unbindResult(mth, insn);
InsnList.remove(block, insn);
InsnRemover.unbindAllArgs(mth, phiInsn);
header.getInstructions().clear();
ternInsn.rebindArgs();
@@ -14,6 +14,9 @@ import jadx.core.Consts;
import jadx.core.codegen.json.JsonMappingGen;
import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
@@ -195,7 +198,7 @@ public class RenameVisitor extends AbstractVisitor {
Set<String> names = new HashSet<>(methods.size());
for (MethodNode mth : methods) {
String signature = mth.getMethodInfo().makeSignature(true, false);
if (!names.add(signature)) {
if (!names.add(signature) && canRename(mth)) {
deobfuscator.forceRenameMethod(mth);
mth.addAttr(new RenameReasonAttr("collision with other method in class"));
}
@@ -203,6 +206,23 @@ public class RenameVisitor extends AbstractVisitor {
}
}
private static boolean canRename(MethodNode mth) {
if (mth.contains(AFlag.DONT_RENAME)) {
return false;
}
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr != null) {
for (MethodNode relatedMth : overrideAttr.getRelatedMthNodes()) {
if (relatedMth != mth && mth.getParentClass().equals(relatedMth.getParentClass())) {
// ignore rename if exists related method from same class (bridge method in most cases)
// such rename will also rename current method and will not help to resolve name collision
return false;
}
}
}
return true;
}
private static void processRootPackages(Deobfuscator deobfuscator, RootNode root, List<ClassNode> classes) {
Set<String> rootPkgs = collectRootPkgs(classes);
root.getCacheStorage().setRootPkgs(rootPkgs);
@@ -1,7 +1,7 @@
package jadx.core.dex.visitors.shrink;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
import jadx.core.dex.instructions.InsnType;
@@ -30,7 +30,7 @@ final class ArgsInfo {
}
public static List<RegisterArg> getArgs(InsnNode insn) {
List<RegisterArg> args = new LinkedList<>();
List<RegisterArg> args = new ArrayList<>();
addArgs(insn, args);
return args;
}
@@ -1,10 +1,10 @@
package jadx.core.dex.visitors.ssa;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
@@ -81,7 +81,7 @@ public class SSATransform extends AbstractVisitor {
int blocksCount = blocks.size();
BitSet hasPhi = new BitSet(blocksCount);
BitSet processed = new BitSet(blocksCount);
Deque<BlockNode> workList = new LinkedList<>();
Deque<BlockNode> workList = new ArrayDeque<>();
BitSet assignBlocks = la.getAssignBlocks(regNum);
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
@@ -136,7 +136,7 @@ public class SSATransform extends AbstractVisitor {
RenameState initState = RenameState.init(mth);
initPhiInEnterBlock(initState);
Deque<RenameState> stack = new LinkedList<>();
Deque<RenameState> stack = new ArrayDeque<>();
stack.push(initState);
while (!stack.isEmpty()) {
RenameState state = stack.pop();
@@ -913,25 +913,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
}
boolean fixed = false;
for (ITypeBound bound : typeInfo.getBounds()) {
if (bound.getBound() == BoundEnum.USE
&& fixBooleanUsage(mth, bound)) {
for (RegisterArg arg : new ArrayList<>(var.getUseList())) {
if (fixBooleanUsage(mth, arg)) {
fixed = true;
}
}
return fixed;
}
private boolean fixBooleanUsage(MethodNode mth, ITypeBound bound) {
ArgType boundType = bound.getType();
private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) {
ArgType boundType = boundArg.getInitType();
if (boundType == ArgType.BOOLEAN
|| (boundType.isTypeKnown() && !boundType.isPrimitive())) {
return false;
}
RegisterArg boundArg = bound.getArg();
if (boundArg == null) {
return false;
}
InsnNode insn = boundArg.getParentInsn();
if (insn == null || insn.getType() == InsnType.IF) {
return false;
@@ -17,6 +17,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -228,6 +229,18 @@ public class InsnRemover {
removeAll(block.getInstructions(), insns);
}
public static void removeAllAndUnbind(MethodNode mth, IContainer container, List<InsnNode> insns) {
unbindInsns(mth, insns);
RegionUtils.visitBlocks(mth, container, b -> removeAll(b.getInstructions(), insns));
}
public static void removeAllAndUnbind(MethodNode mth, List<InsnNode> insns) {
unbindInsns(mth, insns);
for (BlockNode block : mth.getBasicBlocks()) {
removeAll(block.getInstructions(), insns);
}
}
public static void removeAllWithoutUnbind(BlockNode block, List<InsnNode> insns) {
removeAll(block.getInstructions(), insns);
}
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
@@ -26,6 +27,8 @@ import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlockAttr;
import jadx.core.dex.visitors.regions.AbstractRegionVisitor;
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class RegionUtils {
@@ -146,20 +149,6 @@ public class RegionUtils {
}
}
@Nullable
public static IContainer getLastRegion(@Nullable IContainer container) {
if (container == null) {
return null;
}
if (container instanceof IBlock || container instanceof IBranchRegion) {
return container;
}
if (container instanceof IRegion) {
return getLastRegion(Utils.last(((IRegion) container).getSubBlocks()));
}
throw new JadxRuntimeException(unknownContainerType(container));
}
public static boolean isExitBlock(MethodNode mth, IContainer container) {
if (container instanceof BlockNode) {
return BlockUtils.isExitBlock(mth, (BlockNode) container);
@@ -487,4 +476,13 @@ public class RegionUtils {
}
return "Unknown container type: " + container.getClass();
}
public static void visitBlocks(MethodNode mth, IContainer container, Consumer<IBlock> visitor) {
DepthRegionTraversal.traverse(mth, container, new AbstractRegionVisitor() {
@Override
public void processBlock(MethodNode mth, IBlock block) {
visitor.accept(block);
}
});
}
}
@@ -4,10 +4,8 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
@@ -175,7 +173,7 @@ public class ManifestAttributes {
if (attr.getType() == MAttrType.ENUM) {
return attr.getValues().get(value);
} else if (attr.getType() == MAttrType.FLAG) {
List<String> flagList = new LinkedList<>();
List<String> flagList = new ArrayList<>();
List<Long> attrKeys = new ArrayList<>(attr.getValues().keySet());
attrKeys.sort((a, b) -> Long.compare(b, a)); // sort descending
for (Long key : attrKeys) {
@@ -188,7 +186,7 @@ public class ManifestAttributes {
value ^= key;
}
}
return flagList.stream().collect(Collectors.joining("|"));
return String.join("|", flagList);
}
return null;
}
@@ -28,9 +28,12 @@ public class ParserConstants {
protected static final int RES_XML_LAST_CHUNK_TYPE = 0x017f;
protected static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
protected static final int RES_TABLE_TYPE_TYPE = 0x0201;
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
protected static final int RES_TABLE_PACKAGE_TYPE = 0x0200; // 512
protected static final int RES_TABLE_TYPE_TYPE = 0x0201; // 513
protected static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202; // 514
protected static final int RES_TABLE_TYPE_LIBRARY = 0x0203; // 515
protected static final int RES_TABLE_TYPE_OVERLAY = 0x0204; // 516
protected static final int RES_TABLE_TYPE_STAGED_ALIAS = 0x0206; // 517
/**
* Type constants
@@ -153,13 +153,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
while (is.getPos() < endPos) {
long chunkStart = is.getPos();
int type = is.readInt16();
if (type == RES_NULL_TYPE) {
continue;
}
if (type == RES_TABLE_TYPE_SPEC_TYPE) {
parseTypeSpecChunk();
} else if (type == RES_TABLE_TYPE_TYPE) {
parseTypeChunk(chunkStart, pkg);
LOG.trace("res package chunk start at {} type {}", chunkStart, type);
switch (type) {
case RES_NULL_TYPE:
LOG.info("Null chunk type encountered at offset {}", chunkStart);
break;
case RES_TABLE_TYPE_TYPE: // 0x0201
parseTypeChunk(chunkStart, pkg);
break;
case RES_TABLE_TYPE_SPEC_TYPE: // 0x0202
parseTypeSpecChunk(chunkStart);
break;
case RES_TABLE_TYPE_LIBRARY: // 0x0203
parseLibraryTypeChunk(chunkStart);
break;
case RES_TABLE_TYPE_OVERLAY: // 0x0204
throw new IOException(
String.format("Encountered unsupported chunk type TYPE_OVERLAY at offset 0x%x ", chunkStart));
case RES_TABLE_TYPE_STAGED_ALIAS: // 0x0206
throw new IOException(
String.format("Encountered unsupported chunk type TYPE_STAGED_ALIAS at offset 0x%x ", chunkStart));
default:
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
}
}
return pkg;
@@ -192,10 +207,10 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
}
@SuppressWarnings("unused")
private void parseTypeSpecChunk() throws IOException {
private void parseTypeSpecChunk(long chunkStart) throws IOException {
is.checkInt16(0x0010, "Unexpected type spec header size");
/* int size = */
is.readInt32();
int chunkSize = is.readInt32();
long expectedEndPos = chunkStart + chunkSize;
int id = is.readInt8();
is.skip(3);
@@ -203,6 +218,28 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
for (int i = 0; i < entryCount; i++) {
int entryFlag = is.readInt32();
}
if (is.getPos() != expectedEndPos) {
throw new IOException(String.format("Error reading type spec chunk at offset 0x%x", chunkStart));
}
}
private void parseLibraryTypeChunk(long chunkStart) throws IOException {
LOG.trace("parsing library type chunk starting at offset {}", chunkStart);
is.checkInt16(12, "Unexpected header size");
int chunkSize = is.readInt32();
long expectedEndPos = chunkStart + chunkSize;
int count = is.readInt32();
for (int i = 0; i < count; i++) {
int packageId = is.readInt32();
String packageName = is.readString16Fixed(128);
LOG.info("Found resource shared library {}, pkgId: {}", packageName, packageId);
if (is.getPos() > expectedEndPos) {
throw new IOException("reading after chunk end");
}
}
if (is.getPos() != expectedEndPos) {
throw new IOException(String.format("Error reading library chunk at offset 0x%x", chunkStart));
}
}
private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
@@ -241,6 +278,13 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
parseEntry(pkg, id, i, config.getQualifiers());
}
}
if (chunkEnd > is.getPos()) {
// Skip remaining unknown data in this chunk (e.g. type 8 entries")
long skipSize = chunkEnd - is.getPos();
LOG.debug("Unknown data at the end of type chunk encountered, skipping {} bytes and continuing at offset {}", skipSize,
chunkEnd);
is.skip(skipSize);
}
}
private void parseEntry(PackageChunk pkg, int typeId, int entryId, String config) throws IOException {
@@ -49,7 +49,7 @@ public class ResourcesSaver implements Runnable {
private void save(ResContainer rc, File outDir) {
File outFile = new File(outDir, rc.getFileName());
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
LOG.error("Invalid resource name or path traversal attack detected: {}", outFile.getPath());
return;
}
saveToFile(rc, outFile);
@@ -6,6 +6,7 @@ import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
@SuppressWarnings("CommentedOutCode")
public class TestBooleanToInt extends SmaliTest {
// @formatter:off
@@ -0,0 +1,40 @@
package jadx.tests.integration.conditions;
import org.junit.jupiter.api.Test;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
@SuppressWarnings("CommentedOutCode")
public class TestBooleanToInt2 extends SmaliTest {
// @formatter:off
/*
public static class TestCls {
public void test() {
boolean v = getValue();
use1(Integer.valueOf(v));
use2(v);
}
private boolean getValue() {
return false;
}
private void use1(Integer v) {
}
private void use2(int v) {
}
}
*/
// @formatter:on
@Test
public void test() {
assertThat(getClassNodeFromSmali())
.code()
.containsOne("use1(Integer.valueOf(value ? 1 : 0));")
.containsOne("use2(value ? 1 : 0);");
}
}
@@ -0,0 +1,37 @@
package jadx.tests.integration.conditions;
import org.junit.jupiter.api.Test;
import jadx.api.ICodeWriter;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestElseIfCodeStyle extends IntegrationTest {
@SuppressWarnings("unused")
public static class TestCls {
public void test(String str) {
if ("a".equals(str)) {
call(1);
} else if ("b".equals(str)) {
call(2);
} else if ("c".equals(str)) {
call(3);
}
}
private void call(int i) {
}
}
@Test
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.doesNotContain("!\"c\".equals(str)")
.doesNotContain("{" + ICodeWriter.NL + indent(2) + "} else {"); // no empty `then` block
}
}
@@ -1,41 +0,0 @@
package jadx.tests.integration.conditions;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
public class TestSwitchTryBreak extends IntegrationTest {
public static class TestCls {
public void test(int x) {
switch (x) {
case 0:
return;
case 1:
String res;
if ("android".equals(toString())) {
res = "hello";
} else {
try {
if (String.CASE_INSENSITIVE_ORDER != null) {
break;
}
res = "hi";
} catch (Exception e) {
break;
}
}
System.out.println(res);
}
System.out.println("returning");
}
}
@Test
@NotYetImplemented
public void test() {
getClassNode(TestCls.class);
}
}
@@ -1,41 +0,0 @@
package jadx.tests.integration.conditions;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.tests.api.IntegrationTest;
public class TestSwitchTryBreak2 extends IntegrationTest {
public static class TestCls {
public void test(int x) {
switch (x) {
case 0:
return;
case 1:
String res;
if ("android".equals(toString())) {
res = "hello";
} else {
try {
if (x == 5) {
break;
}
res = "hi";
} catch (Exception e) {
break;
}
}
System.out.println(res);
}
System.out.println("returning");
}
}
@Test
@NotYetImplemented
public void test() {
getClassNode(TestCls.class);
}
}
@@ -0,0 +1,45 @@
package jadx.tests.integration.enums;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestEnumsWithCustomInit extends IntegrationTest {
public enum TestCls {
ONE("I"),
TWO("II"),
THREE("III");
public static final Map<String, TestCls> MAP = new HashMap<>();
static {
for (TestCls value : values()) {
MAP.put(value.toString(), value);
}
}
private final String str;
TestCls(String str) {
this.str = str;
}
public String toString() {
return str;
}
}
@Test
public void test() {
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("ONE(\"I\"),")
.doesNotContain("new TestEnumsWithCustomInit$TestCls(");
}
}
@@ -0,0 +1,39 @@
package jadx.tests.integration.enums;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.extensions.profiles.TestProfile;
import jadx.tests.api.extensions.profiles.TestWithProfiles;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestEnumsWithTernary extends IntegrationTest {
public enum TestCls {
FIRST(useNumber() ? "1" : "A"),
SECOND(useNumber() ? "2" : "B"),
ANY(useNumber() ? "1" : "2");
private final String str;
TestCls(String str) {
this.str = str;
}
public String getStr() {
return str;
}
public static boolean useNumber() {
return false;
}
}
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.D8_J8 })
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("ANY(useNumber() ? \"1\" : \"2\");")
.doesNotContain("static {");
}
}
@@ -0,0 +1,47 @@
package jadx.tests.integration.inline;
import org.assertj.core.api.Condition;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.extensions.profiles.TestProfile;
import jadx.tests.api.extensions.profiles.TestWithProfiles;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestSyntheticBridgeRename extends IntegrationTest {
@SuppressWarnings("InnerClassMayBeStatic")
public static class TestCls {
private abstract class Inner<V> {
public abstract V get(String value);
}
public class IntInner extends Inner<Integer> {
public Integer get(String value) {
return value.length();
}
}
public void test() {
IntInner inner = new IntInner();
call(inner.get("a"));
}
private static void call(Integer value) {
}
}
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
public void test() {
ClassNode cls = getClassNode(TestCls.class);
assertThat(searchCls(cls.getInnerClasses(), "IntInner").getMethods())
.as("check that bridge method was generated by compiler")
.haveAtLeastOne(new Condition<>(mth -> mth.getAccessFlags().isBridge(), "bridge"));
assertThat(cls)
.code()
.doesNotContain("mo0get")
.containsOne("call(inner.get(\"a\"));");
}
}
@@ -1,8 +1,8 @@
package jadx.tests.integration.names;
import java.util.ArrayDeque;
import java.util.BitSet;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -26,7 +26,7 @@ public class TestNameAssign2 extends IntegrationTest {
int blocksCount = blocks.size();
BitSet hasPhi = new BitSet(blocksCount);
BitSet processed = new BitSet(blocksCount);
Deque<BlockNode> workList = new LinkedList<>();
Deque<BlockNode> workList = new ArrayDeque<>();
BitSet assignBlocks = la.getAssignBlocks(regNum);
for (int id = assignBlocks.nextSetBit(0); id >= 0; id = assignBlocks.nextSetBit(id + 1)) {
@@ -0,0 +1,29 @@
.class public Lconditions/TestBooleanToInt2;
.super Ljava/lang/Object;
.method public test()V
.registers 3
invoke-direct {p0}, Lconditions/TestBooleanToInt2;->getValue()Z
move-result v0
invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
move-result-object v1
invoke-direct {p0, v1}, Lconditions/TestBooleanToInt2;->use1(Ljava/lang/Integer;)V
invoke-direct {p0, v0}, Lconditions/TestBooleanToInt2;->use2(I)V
return-void
.end method
.method private getValue()Z
.registers 2
const/4 v0, 0x0
return v0
.end method
.method private use1(Ljava/lang/Integer;)V
.registers 2
return-void
.end method
.method private use2(I)V
.registers 2
return-void
.end method
+10 -10
View File
@@ -9,25 +9,25 @@ dependencies {
implementation(project(':jadx-core'))
implementation(project(":jadx-cli"))
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.11'
implementation 'ch.qos.logback:logback-classic:1.3.4'
implementation 'com.fifesoft:rsyntaxtextarea:3.2.0'
implementation 'com.fifesoft:rsyntaxtextarea:3.3.0'
implementation files('libs/jfontchooser-1.0.5.jar')
implementation 'hu.kazocsaba:image-viewer:1.2.3'
implementation 'com.formdev:flatlaf:2.4'
implementation 'com.formdev:flatlaf-intellij-themes:2.4'
implementation 'com.formdev:flatlaf-extras:2.4'
implementation 'com.formdev:svgSalamander:1.1.3'
implementation 'com.formdev:flatlaf:2.6'
implementation 'com.formdev:flatlaf-intellij-themes:2.6'
implementation 'com.formdev:flatlaf-extras:2.6'
implementation 'com.formdev:svgSalamander:1.1.4'
implementation 'com.google.code.gson:gson:2.9.1'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-text:1.9'
implementation 'org.apache.commons:commons-text:1.10.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
implementation "com.github.akarnokd:rxjava2-swing:0.3.7"
implementation 'com.android.tools.build:apksig:7.2.2'
implementation 'io.github.hqktech:jdwp:1.0'
implementation 'com.android.tools.build:apksig:7.3.1'
implementation 'io.github.skylot:jdwp:2.0.0'
// TODO: Switch back to upstream once this PR gets merged:
// https://github.com/FabricMC/mapping-io/pull/19
@@ -90,7 +90,7 @@ launch4j {
windowTitle = 'jadx'
companyName = 'jadx'
jreMinVersion = '11'
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC']
jvmOptions = ['-Dawt.useSystemAAFontSettings=lcd', '-Dswing.aatext=true', '-XX:+UseG1GC', '-Djava.util.Arrays.useLegacyMergeSort=true']
jreRuntimeBits = "64"
bundledJre64Bit = true
initialHeapPercent = 5
@@ -44,11 +44,12 @@ import jadx.gui.ui.panel.JDebuggerPanel.ValueTreeNode;
import jadx.gui.utils.NLS;
public final class DebugController implements SmaliDebugger.SuspendListener, IDebugController {
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
private static final Logger LOG = LoggerFactory.getLogger(DebugController.class);
private static final String ONCREATE_SIGNATURE = "onCreate(Landroid/os/Bundle;)V";
private static final Map<String, RuntimeType> TYPE_MAP = new HashMap<>();
private static final RuntimeType[] POSSIBLE_TYPES = { RuntimeType.OBJECT, RuntimeType.INT, RuntimeType.LONG };
private static final int DEFAULT_CACHE_SIZE = 512;
private JDebuggerPanel debuggerPanel;
private SmaliDebugger debugger;
@@ -1115,7 +1116,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
private long thisID;
public FrameNode(long threadID, SmaliDebugger.Frame frame) {
cache = new StringBuilder(16);
cache = new StringBuilder(DEFAULT_CACHE_SIZE);
this.frame = frame;
this.threadID = threadID;
regNodes = Collections.emptyList();
@@ -1153,7 +1154,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
public void setSignatures(String clsSig, String mthSig) {
this.clsSig = clsSig;
this.mthSig = mthSig;
this.cache.delete(0, this.cache.length());
resetCache();
}
public String getClsSig() {
@@ -1167,7 +1168,7 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
public void updateCodeOffset(long codeOffset) {
this.codeOffset = codeOffset;
if (this.codeOffset > -1) {
this.cache.delete(0, this.cache.length());
resetCache();
}
}
@@ -1209,27 +1210,34 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe
}
}
private void resetCache() {
// Do not reuse thee existing cache instance as this can result in
// multi-threading access issues in case toString() method is active
this.cache = new StringBuilder(DEFAULT_CACHE_SIZE);
}
@Override
public String toString() {
if (cache.length() == 0) {
StringBuilder sbCache = cache;
if (sbCache.length() == 0) {
long off = getCodeOffset();
if (off < 0) {
cache.append(String.format("index: %-4d ", off));
sbCache.append(String.format("index: %-4d ", off));
} else {
cache.append(String.format("index: %04x ", off));
sbCache.append(String.format("index: %04x ", off));
}
if (clsSig == null) {
cache.append("clsID: ").append(frame.getClassID());
sbCache.append("clsID: ").append(frame.getClassID());
} else {
cache.append(clsSig).append("->");
sbCache.append(clsSig).append("->");
}
if (mthSig == null) {
cache.append(" mthID: ").append(frame.getMethodID());
sbCache.append(" mthID: ").append(frame.getMethodID());
} else {
cache.append(mthSig);
sbCache.append(mthSig);
}
}
return cache.toString();
return sbCache.toString();
}
}
@@ -1,23 +1,23 @@
package jadx.gui.device.debugger;
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
abstract class EventListenerAdapter {
void onVMStart(VMStartEvent event) {
@@ -0,0 +1,390 @@
package jadx.gui.device.debugger;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.ui.panel.LogcatPanel;
public class LogcatController {
private static final Logger LOG = LoggerFactory.getLogger(LogcatController.class);
private final ADBDevice adbDevice;
private final LogcatPanel logcatPanel;
private Timer timer;
private final String timezone;
private LogcatInfo recent = null;
private ArrayList<LogcatInfo> events = new ArrayList<>();
private LogcatFilter filter = new LogcatFilter(null, null);
private String status = "null";
public LogcatController(LogcatPanel logcatPanel, ADBDevice adbDevice) throws IOException {
this.adbDevice = adbDevice;
this.logcatPanel = logcatPanel;
this.timezone = adbDevice.getTimezone();
this.startLogcat();
}
public void startLogcat() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
getLog();
}
}, 0, 1000);
this.status = "running";
}
public void stopLogcat() {
timer.cancel();
this.status = "stopped";
}
public String getStatus() {
return this.status;
}
public void clearLogcat() {
try {
adbDevice.clearLogcat();
clearEvents();
} catch (IOException e) {
LOG.error("Failed to clear Logcat", e);
}
}
private void getLog() {
if (!logcatPanel.isReady()) {
return;
}
try {
byte[] buf;
if (recent == null) {
buf = adbDevice.getBinaryLogcat();
} else {
buf = adbDevice.getBinaryLogcat(recent.getAfterTimestamp());
}
if (buf == null) {
return;
}
ByteBuffer in = ByteBuffer.wrap(buf);
in.order(ByteOrder.LITTLE_ENDIAN);
while (in.remaining() > 20) {
LogcatInfo eInfo = null;
byte[] msgBuf;
short eLen = in.getShort();
short eHdrLen = in.getShort();
if (eLen + eHdrLen > in.remaining()) {
return;
}
switch (eHdrLen) {
case 20: // header length 20 == version 1
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 24: // header length 24 == version 2 / 3
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
case 28: // header length 28 == version 4
eInfo = new LogcatInfo(eLen, eHdrLen, in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(), in.getInt(),
in.get());
msgBuf = new byte[eLen];
in.get(msgBuf, 0, eLen - 1);
eInfo.setMsg(msgBuf);
break;
default:
break;
}
if (eInfo == null) {
return;
}
if (recent == null) {
recent = eInfo;
} else if (recent.getInstant().isBefore(eInfo.getInstant())) {
recent = eInfo;
}
if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
events.add(eInfo);
}
} catch (Exception e) {
LOG.error("Failed to get logcat message", e);
}
}
public boolean reload() {
stopLogcat();
boolean ok = logcatPanel.clearLogcatArea();
if (ok) {
events.forEach((eInfo) -> {
if (filter.doFilter(eInfo)) {
logcatPanel.log(eInfo);
}
});
startLogcat();
}
return true;
}
public void clearEvents() {
this.recent = null;
this.events = new ArrayList<>();
}
public void exit() {
stopLogcat();
filter = new LogcatFilter(null, null);
recent = null;
}
public LogcatFilter getFilter() {
return this.filter;
}
public class LogcatFilter {
private final ArrayList<Integer> pid;
private ArrayList<Byte> msgType = new ArrayList<Byte>() {
{
add((byte) 1);
add((byte) 2);
add((byte) 3);
add((byte) 4);
add((byte) 5);
add((byte) 6);
add((byte) 7);
add((byte) 8);
}
};
public LogcatFilter(ArrayList<Integer> pid, ArrayList<Byte> msgType) {
if (pid != null) {
this.pid = pid;
} else {
this.pid = new ArrayList<>();
}
if (msgType != null) {
this.msgType = msgType;
}
}
public void addPid(int pid) {
if (!this.pid.contains(pid)) {
this.pid.add(pid);
}
}
public void removePid(int pid) {
int pidPos = this.pid.indexOf(pid);
if (pidPos >= 0) {
this.pid.remove(pidPos);
}
}
public void togglePid(int pid, boolean state) {
if (state) {
addPid(pid);
} else {
removePid(pid);
}
}
public void addMsgType(byte msgType) {
if (!this.msgType.contains(msgType)) {
this.msgType.add(msgType);
}
}
public void removeMsgType(byte msgType) {
int typePos = this.msgType.indexOf(msgType);
if (typePos >= 0) {
this.msgType.remove(typePos);
}
}
public void toggleMsgType(byte msgType, boolean state) {
if (state) {
addMsgType(msgType);
} else {
removeMsgType(msgType);
}
}
public boolean doFilter(LogcatInfo inInfo) {
if (pid.contains(inInfo.getPid())) {
return msgType.contains(inInfo.getMsgType());
}
return false;
}
public ArrayList<LogcatInfo> getFilteredList(ArrayList<LogcatInfo> inInfoList) {
ArrayList<LogcatInfo> outInfoList = new ArrayList<LogcatInfo>();
inInfoList.forEach((inInfo) -> {
if (doFilter(inInfo)) {
outInfoList.add(inInfo);
}
});
return outInfoList;
}
}
public class LogcatInfo {
private String msg;
private final byte msgType;
private final int nsec;
private final int pid;
private final int sec;
private final int tid;
private final short hdrSize;
private final short len;
private final short version;
private int lid;
private int uid;
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 1;
}
// Version 2 and 3 both have the same arguments
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.version = 3;
}
public LogcatInfo(short len, short hdrSize, int pid, int tid, int sec, int nsec, int lid, int uid, byte msgType) {
this.hdrSize = hdrSize;
this.len = len;
this.lid = lid;
this.msgType = msgType;
this.nsec = nsec;
this.pid = pid;
this.sec = sec;
this.tid = tid;
this.uid = uid;
this.version = 4;
}
public void setMsg(byte[] msg) {
this.msg = new String(msg);
}
public short getVersion() {
return this.version;
}
public short getLen() {
return this.len;
}
public short getHeaderLen() {
return this.hdrSize;
}
public int getPid() {
return this.pid;
}
public int getTid() {
return this.tid;
}
public int getSec() {
return this.sec;
}
public int getNSec() {
return this.nsec;
}
public int getLid() {
return this.lid;
}
public int getUid() {
return this.uid;
}
public Instant getInstant() {
return Instant.ofEpochSecond(getSec(), getNSec());
}
public String getTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant());
}
public String getAfterTimestamp() {
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.of(timezone));
return dtFormat.format(getInstant().plusMillis(1));
}
public byte getMsgType() {
return this.msgType;
}
public String getMsgTypeString() {
switch (getMsgType()) {
case 0:
return "Unknown";
case 1:
return "Default";
case 2:
return "Verbose";
case 3:
return "Debug";
case 4:
return "Info";
case 5:
return "Warn";
case 6:
return "Error";
case 7:
return "Fatal";
case 8:
return "Silent";
default:
return "Unknown";
}
}
public String getMsg() {
return this.msg;
}
}
}
@@ -1,6 +1,6 @@
package jadx.gui.device.debugger;
import io.github.hqktech.JDWP;
import io.github.skylot.jdwp.JDWP;
public enum RuntimeType {
ARRAY(91, "[]"),
@@ -21,56 +21,56 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.hqktech.JDWP;
import io.github.hqktech.JDWP.ArrayReference.Length.LengthReplyData;
import io.github.hqktech.JDWP.ByteBuffer;
import io.github.hqktech.JDWP.Event.Composite.BreakpointEvent;
import io.github.hqktech.JDWP.Event.Composite.ClassPrepareEvent;
import io.github.hqktech.JDWP.Event.Composite.ClassUnloadEvent;
import io.github.hqktech.JDWP.Event.Composite.EventData;
import io.github.hqktech.JDWP.Event.Composite.ExceptionEvent;
import io.github.hqktech.JDWP.Event.Composite.FieldAccessEvent;
import io.github.hqktech.JDWP.Event.Composite.FieldModificationEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodEntryEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodExitEvent;
import io.github.hqktech.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnterEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorContendedEnteredEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitEvent;
import io.github.hqktech.JDWP.Event.Composite.MonitorWaitedEvent;
import io.github.hqktech.JDWP.Event.Composite.SingleStepEvent;
import io.github.hqktech.JDWP.Event.Composite.ThreadDeathEvent;
import io.github.hqktech.JDWP.Event.Composite.ThreadStartEvent;
import io.github.hqktech.JDWP.Event.Composite.VMDeathEvent;
import io.github.hqktech.JDWP.Event.Composite.VMStartEvent;
import io.github.hqktech.JDWP.EventRequest.Set.ClassMatchRequest;
import io.github.hqktech.JDWP.EventRequest.Set.CountRequest;
import io.github.hqktech.JDWP.EventRequest.Set.LocationOnlyRequest;
import io.github.hqktech.JDWP.EventRequest.Set.StepRequest;
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
import io.github.hqktech.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
import io.github.hqktech.JDWP.ObjectReference;
import io.github.hqktech.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
import io.github.hqktech.JDWP.ObjectReference.SetValues.FieldValueSetter;
import io.github.hqktech.JDWP.Packet;
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
import io.github.hqktech.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
import io.github.hqktech.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
import io.github.hqktech.JDWP.ReferenceType.Signature.SignatureReplyData;
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesReplyData;
import io.github.hqktech.JDWP.StackFrame.GetValues.GetValuesSlots;
import io.github.hqktech.JDWP.StackFrame.SetValues.SlotValueSetter;
import io.github.hqktech.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
import io.github.hqktech.JDWP.StringReference.Value.ValueReplyData;
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyData;
import io.github.hqktech.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
import io.github.hqktech.JDWP.ThreadReference.Name.NameReplyData;
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
import io.github.hqktech.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
import io.github.hqktech.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
import io.github.hqktech.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
import io.github.skylot.jdwp.JDWP;
import io.github.skylot.jdwp.JDWP.ArrayReference.Length.LengthReplyData;
import io.github.skylot.jdwp.JDWP.ByteBuffer;
import io.github.skylot.jdwp.JDWP.Event.Composite.BreakpointEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassPrepareEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ClassUnloadEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.EventData;
import io.github.skylot.jdwp.JDWP.Event.Composite.ExceptionEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldAccessEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.FieldModificationEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodEntryEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MethodExitWithReturnValueEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnterEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorContendedEnteredEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.MonitorWaitedEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.SingleStepEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadDeathEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.ThreadStartEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.VMDeathEvent;
import io.github.skylot.jdwp.JDWP.Event.Composite.VMStartEvent;
import io.github.skylot.jdwp.JDWP.EventRequest.Set.ClassMatchRequest;
import io.github.skylot.jdwp.JDWP.EventRequest.Set.CountRequest;
import io.github.skylot.jdwp.JDWP.EventRequest.Set.LocationOnlyRequest;
import io.github.skylot.jdwp.JDWP.EventRequest.Set.StepRequest;
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarTableWithGenericData;
import io.github.skylot.jdwp.JDWP.Method.VariableTableWithGeneric.VarWithGenericSlot;
import io.github.skylot.jdwp.JDWP.ObjectReference;
import io.github.skylot.jdwp.JDWP.ObjectReference.ReferenceType.ReferenceTypeReplyData;
import io.github.skylot.jdwp.JDWP.ObjectReference.SetValues.FieldValueSetter;
import io.github.skylot.jdwp.JDWP.Packet;
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericData;
import io.github.skylot.jdwp.JDWP.ReferenceType.FieldsWithGeneric.FieldsWithGenericReplyData;
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericData;
import io.github.skylot.jdwp.JDWP.ReferenceType.MethodsWithGeneric.MethodsWithGenericReplyData;
import io.github.skylot.jdwp.JDWP.ReferenceType.Signature.SignatureReplyData;
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesReplyData;
import io.github.skylot.jdwp.JDWP.StackFrame.GetValues.GetValuesSlots;
import io.github.skylot.jdwp.JDWP.StackFrame.SetValues.SlotValueSetter;
import io.github.skylot.jdwp.JDWP.StackFrame.ThisObject.ThisObjectReplyData;
import io.github.skylot.jdwp.JDWP.StringReference.Value.ValueReplyData;
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyData;
import io.github.skylot.jdwp.JDWP.ThreadReference.Frames.FramesReplyDataFrames;
import io.github.skylot.jdwp.JDWP.ThreadReference.Name.NameReplyData;
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericData;
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllClassesWithGeneric.AllClassesWithGenericReplyData;
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyData;
import io.github.skylot.jdwp.JDWP.VirtualMachine.AllThreads.AllThreadsReplyDataThreads;
import io.github.skylot.jdwp.JDWP.VirtualMachine.CreateString.CreateStringReplyData;
import io.reactivex.annotations.NonNull;
import jadx.api.plugins.input.data.AccessFlags;
@@ -10,6 +10,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,7 +27,7 @@ public class ADBDevice {
private static final Logger LOG = LoggerFactory.getLogger(ADBDevice.class);
private static final String CMD_TRACK_JDWP = "000atrack-jdwp";
private static final Pattern TIMESTAMP_FORMAT = Pattern.compile("^[0-9]{2}\\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}$");
ADBDeviceInfo info;
String androidReleaseVer;
volatile Socket jdwpListenerSock;
@@ -120,6 +122,49 @@ public class ADBDevice {
return -1;
}
/**
* @Return binary output of logcat
*/
public byte[] getBinaryLogcat() throws IOException {
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
String cmd = "logcat -dB";
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
}
/**
* @Return binary output of logcat after provided timestamp
* Timestamp is in the format 09-08 02:18:03.131
*/
public byte[] getBinaryLogcat(String timestamp) throws IOException {
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
Matcher matcher = TIMESTAMP_FORMAT.matcher(timestamp);
if (!matcher.find()) {
LOG.error("Invalid Logcat Timestamp " + timestamp);
}
String cmd = "logcat -dB -t \"" + timestamp + "\"";
return ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
}
/**
* @Return binary output of logcat -c
*/
public void clearLogcat() throws IOException {
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
String cmd = "logcat -c";
ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
}
/**
* @return Timezone for the attached android device
*/
public String getTimezone() throws IOException {
Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort());
String cmd = "getprop persist.sys.timezone";
byte[] tz = ADB.execShellCommandRaw(info.getSerial(), cmd, socket.getOutputStream(), socket.getInputStream());
return new String(tz).trim();
}
public String getAndroidReleaseVersion() {
if (!StringUtils.isEmpty(androidReleaseVer)) {
return androidReleaseVer;
@@ -160,15 +205,15 @@ public class ADBDevice {
}
public List<Process> getProcessByPkg(String pkg) throws IOException {
return getProcessList("ps | grep " + pkg, 0);
return getProcessList("ps | grep " + pkg);
}
@NonNull
public List<Process> getProcessList() throws IOException {
return getProcessList("ps", 1);
return getProcessList("ps");
}
private List<Process> getProcessList(String cmd, int index) throws IOException {
private List<Process> getProcessList(String cmd) throws IOException {
try (Socket socket = ADB.connect(info.getAdbHost(), info.getAdbPort())) {
List<Process> procs = new ArrayList<>();
byte[] payload = ADB.execShellCommandRaw(info.getSerial(), cmd,
@@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
public class QuarkDialog extends JDialog {
private static final long serialVersionUID = 4855753773520368215L;
@@ -59,7 +60,7 @@ public class QuarkDialog extends JDialog {
description.setAlignmentX(0.5f);
fileSelectCombo = new JComboBox<>(files.toArray(new Path[0]));
fileSelectCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getFileName().toString()));
fileSelectCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new NodeLabel(value.getFileName().toString()));
JPanel textPane = new JPanel();
textPane.add(description);
@@ -44,6 +44,7 @@ import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.ui.NodeLabel;
public class QuarkReportPanel extends ContentPanel {
private static final long serialVersionUID = -242266836695889206L;
@@ -211,7 +212,7 @@ public class QuarkReportPanel extends ContentPanel {
@Override
public Component render() {
JLabel label = new JLabel(((String) getUserObject()));
JLabel label = new NodeLabel(((String) getUserObject()));
label.setFont(bold ? boldFont : font);
label.setIcon(null);
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
@@ -320,7 +321,7 @@ public class QuarkReportPanel extends ContentPanel {
@Override
public Component render() {
JLabel label = new JLabel(mth.toString());
JLabel label = new NodeLabel(mth.toString());
label.setFont(font);
label.setIcon(jnode.getIcon());
label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
@@ -5,7 +5,6 @@ import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -35,7 +34,6 @@ public class SearchTask extends CancelableBackgroundTask {
private final TaskProgress taskProgress = new TaskProgress();
private final AtomicInteger resultsCount = new AtomicInteger(0);
private final AtomicBoolean complete = new AtomicBoolean(false);
private int resultsLimit;
private Future<TaskStatus> future;
@@ -60,7 +58,6 @@ public class SearchTask extends CancelableBackgroundTask {
throw new IllegalStateException("Previous task not yet finished");
}
resetCancel();
complete.set(false);
resultsCount.set(0);
taskProgress.updateTotal(jobs.stream().mapToInt(s -> s.getProvider().total()).sum());
future = backgroundExecutor.execute(this);
@@ -74,7 +71,6 @@ public class SearchTask extends CancelableBackgroundTask {
this.resultsListener.accept(resultNode);
if (resultsLimit != 0 && resultsCount.incrementAndGet() >= resultsLimit) {
cancel();
complete.set(false);
return true;
}
return false;
@@ -79,6 +79,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean codeAreaLineWrap = false;
private int srhResourceSkipSize = 1000;
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
private int searchResultsPerPage = 50;
private boolean useAutoSearch = true;
private boolean keepCommonDialogOpen = false;
private boolean smaliAreaShowBytecode = false;
@@ -541,6 +542,14 @@ public class JadxSettings extends JadxCLIArgs {
srhResourceFileExt = all.trim();
}
public int getSearchResultsPerPage() {
return searchResultsPerPage;
}
public void setSearchResultsPerPage(int searchResultsPerPage) {
this.searchResultsPerPage = searchResultsPerPage;
}
public boolean isUseAutoSearch() {
return useAutoSearch;
}
@@ -45,10 +45,6 @@ import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -676,48 +672,26 @@ public class JadxSettingsWindow extends JDialog {
}
private SettingsGroup makeSearchResGroup() {
SettingsGroup group = new SettingsGroup(NLS.str("preferences.search_res_title"));
int prevSize = settings.getSrhResourceSkipSize();
String prevExts = settings.getSrhResourceFileExt();
SpinnerNumberModel sizeLimitModel = new SpinnerNumberModel(prevSize,
0, Integer.MAX_VALUE, 1);
JSpinner spinner = new JSpinner(sizeLimitModel);
JSpinner resultsPerPage = new JSpinner(
new SpinnerNumberModel(settings.getSearchResultsPerPage(), 0, Integer.MAX_VALUE, 1));
resultsPerPage.addChangeListener(ev -> settings.setSearchResultsPerPage((Integer) resultsPerPage.getValue()));
JSpinner sizeLimit = new JSpinner(
new SpinnerNumberModel(settings.getSrhResourceSkipSize(), 0, Integer.MAX_VALUE, 1));
sizeLimit.addChangeListener(ev -> settings.setSrhResourceSkipSize((Integer) sizeLimit.getValue()));
JTextField fileExtField = new JTextField();
group.addRow(NLS.str("preferences.res_skip_file"), spinner);
group.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener((ev) -> {
String ext = fileExtField.getText();
settings.setSrhResourceFileExt(ext);
}));
fileExtField.setText(settings.getSrhResourceFileExt());
spinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int size = (Integer) spinner.getValue();
settings.setSrhResourceSkipSize(size);
}
});
fileExtField.getDocument().addDocumentListener(new DocumentListener() {
private void update() {
String ext = fileExtField.getText();
settings.setSrhResourceFileExt(ext);
}
@Override
public void insertUpdate(DocumentEvent e) {
update();
}
@Override
public void removeUpdate(DocumentEvent e) {
update();
}
@Override
public void changedUpdate(DocumentEvent e) {
update();
}
});
fileExtField.setText(prevExts);
return group;
SettingsGroup searchGroup = new SettingsGroup(NLS.str("preferences.search_group_title"));
searchGroup.addRow(NLS.str("preferences.search_results_per_page"), resultsPerPage);
searchGroup.addRow(NLS.str("preferences.res_skip_file"), sizeLimit);
searchGroup.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
return searchGroup;
}
private void needReload() {
@@ -71,6 +71,11 @@ public class CodeNode extends JNode {
return jNode.makeLongStringHtml();
}
@Override
public boolean disableHtml() {
return jNode.disableHtml();
}
@Override
public String getSyntaxName() {
return jNode.getSyntaxName();
@@ -97,6 +97,11 @@ public class JField extends JNode {
return UiUtils.typeStr(field.getType()) + " " + field.getName();
}
@Override
public boolean disableHtml() {
return false;
}
@Override
public boolean hasDescString() {
return false;
@@ -155,6 +155,11 @@ public class JMethod extends JNode {
return UiUtils.typeFormatHtml(name, getReturnType());
}
@Override
public boolean disableHtml() {
return false;
}
@Override
public String makeDescString() {
return UiUtils.typeStr(getReturnType()) + " " + makeBaseString();
@@ -81,6 +81,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return makeLongString();
}
public boolean disableHtml() {
return true;
}
public int getPos() {
JavaNode javaNode = getJavaNode();
if (javaNode == null) {
@@ -142,6 +142,7 @@ import jadx.gui.utils.UiUtils;
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
import jadx.gui.utils.logs.LogCollector;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel;
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
import static javax.swing.KeyStroke.getKeyStroke;
@@ -1281,6 +1282,7 @@ public class MainWindow extends JFrame {
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused);
if (value instanceof JNode) {
JNode jNode = (JNode) value;
NodeLabel.disableHtml(this, jNode.disableHtml());
setText(jNode.makeStringHtml());
setIcon(jNode.getIcon());
setToolTipText(jNode.getTooltip());
@@ -23,6 +23,7 @@ import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
public class TabComponent extends JPanel {
private static final long serialVersionUID = -8147035487543610321L;
@@ -58,7 +59,7 @@ public class TabComponent extends JPanel {
} else {
tabTitle = node.makeLongStringHtml();
}
label = new JLabel(tabTitle);
label = new NodeLabel(tabTitle, node.disableHtml());
label.setFont(getLabelFont());
String toolTip = contentPanel.getTabTooltip();
if (toolTip != null) {
@@ -361,7 +361,9 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J
debugSetter.name,
debugSetter.device.getDeviceInfo().getAdbHost(),
debugSetter.forwardTcpPort,
debugSetter.ver);
debugSetter.ver,
debugSetter.device,
debugSetter.pid);
} catch (Exception e) {
LOG.error("Failed to attach to process", e);
return false;
@@ -55,6 +55,7 @@ import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
@@ -402,9 +403,9 @@ public abstract class CommonSearchDialog extends JFrame {
}
protected final class ResultsTableCellRenderer implements TableCellRenderer {
private final JLabel label;
private final NodeLabel label;
private final RSyntaxTextArea codeArea;
private final JLabel emptyLabel;
private final NodeLabel emptyLabel;
private final Color codeSelectedColor;
private final Color codeBackground;
@@ -414,11 +415,11 @@ public abstract class CommonSearchDialog extends JFrame {
codeArea.setRows(1);
codeBackground = codeArea.getBackground();
codeSelectedColor = codeArea.getSelectionColor();
label = new JLabel();
label = new NodeLabel();
label.setOpaque(true);
label.setFont(codeArea.getFont());
label.setHorizontalAlignment(SwingConstants.LEFT);
emptyLabel = new JLabel();
emptyLabel = new NodeLabel();
emptyLabel.setOpaque(true);
}
@@ -454,8 +455,9 @@ public abstract class CommonSearchDialog extends JFrame {
private Component makeCell(JNode node, int column) {
if (column == 0) {
label.setText(node.makeLongStringHtml());
label.setToolTipText(label.getText());
label.setToolTipText(node.getTooltip());
label.setIcon(node.getIcon());
label.disableHtml(node.disableHtml());
return label;
}
if (!node.hasDescString()) {
@@ -22,7 +22,6 @@ import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import org.jetbrains.annotations.NotNull;
@@ -60,6 +59,7 @@ import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.DocumentUpdateListener;
import jadx.gui.utils.ui.NodeLabel;
public class RenameDialog extends JDialog {
private static final long serialVersionUID = -3269715644416902410L;
@@ -313,7 +313,7 @@ public class RenameDialog extends JDialog {
private void initUI() {
JLabel lbl = new JLabel(NLS.str("popup.rename"));
JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT);
JLabel nodeLabel = NodeLabel.longName(node);
lbl.setLabelFor(nodeLabel);
renameField = new JTextField(40);
@@ -409,7 +409,7 @@ public class SearchDialog extends CommonSearchDialog {
updateTableHighlight();
prepareForSearch();
});
this.searchTask.setResultsLimit(50);
this.searchTask.setResultsLimit(mainWindow.getSettings().getSearchResultsPerPage());
this.searchTask.setProgressListener(this::updateProgress);
this.searchTask.fetchResults();
LOG.debug("Total search items count estimation: {}", this.searchTask.getTaskProgress().total());
@@ -12,7 +12,6 @@ import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import jadx.api.ICodeInfo;
@@ -31,6 +30,7 @@ import jadx.gui.ui.MainWindow;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
public class UsageDialog extends CommonSearchDialog {
private static final long serialVersionUID = -5105405789969134105L;
@@ -147,7 +147,7 @@ public class UsageDialog extends CommonSearchDialog {
Font codeFont = settings.getFont();
JLabel lbl = new JLabel(NLS.str("usage_dialog.label"));
lbl.setFont(codeFont);
JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT);
JLabel nodeLabel = NodeLabel.longName(node);
nodeLabel.setFont(codeFont);
lbl.setLabelFor(nodeLabel);
@@ -29,6 +29,7 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JTree;
@@ -39,10 +40,14 @@ import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.reactivex.annotations.Nullable;
import jadx.core.utils.StringUtils;
import jadx.gui.device.debugger.DebugController;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.treemodel.JClass;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.SmaliArea;
@@ -53,6 +58,7 @@ import jadx.gui.utils.UiUtils;
public class JDebuggerPanel extends JPanel {
private static final long serialVersionUID = -1111111202102181631L;
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute");
private static final ImageIcon ICON_RERUN = UiUtils.openSvgIcon("debugger/rerun");
@@ -76,6 +82,7 @@ public class JDebuggerPanel extends JPanel {
private final transient JSplitPane rightSplitter;
private final transient JSplitPane leftSplitter;
private final transient IDebugController controller;
private final LogcatPanel logcatPanel;
private final transient VarTreePopupMenu varTreeMenu;
private transient KeyEventDispatcher controllerShortCutDispatcher;
@@ -131,11 +138,14 @@ public class JDebuggerPanel extends JPanel {
varTreeMenu = new VarTreePopupMenu(mainWindow);
JPanel loggerPanel = new JPanel(new CardLayout());
JTabbedPane loggerPanel = new JTabbedPane();
logger = new JTextArea();
logger.setEditable(false);
logger.setLineWrap(true);
loggerPanel.add(new JScrollPane(logger));
JScrollPane loggerScroll = new JScrollPane(logger);
loggerPanel.addTab("Debugger Log", null, loggerScroll, null);
this.logcatPanel = new LogcatPanel(this);
loggerPanel.addTab(NLS.str("logcat.logcat"), null, logcatPanel, null);
leftSplitter.setLeftComponent(stackFramePanel);
leftSplitter.setRightComponent(rightSplitter);
@@ -159,6 +169,7 @@ public class JDebuggerPanel extends JPanel {
JOptionPane.OK_CANCEL_OPTION);
if (what == JOptionPane.OK_OPTION) {
controller.exit();
logcatPanel.exit();
} else {
return;
}
@@ -373,10 +384,16 @@ public class JDebuggerPanel extends JPanel {
}
}
public boolean showDebugger(String procName, String host, int port, int androidVer) {
public boolean showDebugger(String procName, String host, int port, int androidVer, ADBDevice device, String pid) {
boolean ok = controller.startDebugger(this, host, port, androidVer);
if (ok) {
log(String.format("Attached %s %s:%d", procName, host, port));
try {
logcatPanel.init(device, pid);
} catch (Exception e) {
log(NLS.str("logcat.error_fail_start"));
LOG.error("Logcat failed to start", e);
}
leftSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerStackFrameSplitterLoc());
rightSplitter.setDividerLocation(mainWindow.getSettings().getDebuggerVarTreeSplitterLoc());
mainWindow.showDebuggerPanel();
@@ -0,0 +1,451 @@
package jadx.gui.ui.panel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoundedRangeModel;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.device.debugger.LogcatController;
import jadx.gui.device.protocol.ADB;
import jadx.gui.device.protocol.ADBDevice;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
public class LogcatPanel extends JPanel {
private static final Logger LOG = LoggerFactory.getLogger(LogcatPanel.class);
StyleContext sc = StyleContext.getDefaultStyleContext();
private final AttributeSet defaultAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#6c71c4"));
private final AttributeSet verboseAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#2aa198"));
private final AttributeSet debugAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#859900"));
private final AttributeSet infoAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#586e75"));
private final AttributeSet warningAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#b58900"));
private final AttributeSet errorAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#dc322f"));
private final AttributeSet fatalAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#d33682"));
private final AttributeSet silentAset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.decode("#002b36"));
private static final ImageIcon ICON_PAUSE = UiUtils.openSvgIcon("debugger/threadFrozen");
private static final ImageIcon ICON_RUN = UiUtils.openSvgIcon("debugger/execute");
private static final ImageIcon CLEAR_LOGCAT = UiUtils.openSvgIcon("debugger/trash");
private transient JTextPane logcatPane;
private final transient JDebuggerPanel debugPanel;
private LogcatController logcatController;
private boolean ready = false;
private List<ADB.Process> procs;
public LogcatPanel(JDebuggerPanel debugPanel) {
this.debugPanel = debugPanel;
}
private ArrayList<Integer> pids;
private JScrollPane logcatScroll;
private int pid;
private final AbstractAction pauseButton = new AbstractAction(NLS.str("logcat.pause"), ICON_PAUSE) {
@Override
public void actionPerformed(ActionEvent e) {
toggleLogcat();
}
};
private final AbstractAction clearButton = new AbstractAction(NLS.str("logcat.clear"), CLEAR_LOGCAT) {
@Override
public void actionPerformed(ActionEvent e) {
clearLogcat();
}
};
public boolean showLogcat() {
ArrayList<String> pkgs = new ArrayList<>();
pids = new ArrayList<>();
JPanel procBox;
for (ADB.Process proc : procs.subList(1, procs.size())) { // skipping first element because it contains the column label
pkgs.add(String.format("[pid: %-6s] %s", proc.pid, proc.name));
pids.add(Integer.valueOf(proc.pid));
}
String[] msgTypes = {
NLS.str("logcat.default"),
NLS.str("logcat.verbose"),
NLS.str("logcat.debug"),
NLS.str("logcat.info"),
NLS.str("logcat.warn"),
NLS.str("logcat.error"),
NLS.str("logcat.fatal"),
NLS.str("logcat.silent") };
Integer[] msgIndex = { 1, 2, 3, 4, 5, 6, 7, 8 };
this.setLayout(new BorderLayout());
logcatPane = new JTextPane();
logcatPane.setEditable(false);
logcatScroll = new JScrollPane(logcatPane);
JToolBar menuPanel = new JToolBar();
CheckCombo procObj = new CheckCombo(NLS.str("logcat.process"), 1, pids.toArray(new Integer[0]), pkgs.toArray(new String[0]));
procBox = procObj.getContent();
procObj.selectAllBut(this.pids.indexOf(this.pid));
JPanel msgTypeBox = new CheckCombo(NLS.str("logcat.level"), 2, msgIndex, msgTypes).getContent();
menuPanel.add(procBox);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(msgTypeBox);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(pauseButton);
menuPanel.add(Box.createRigidArea(new Dimension(5, 0)));
menuPanel.add(clearButton);
this.add(menuPanel, BorderLayout.NORTH);
this.add(logcatScroll, BorderLayout.CENTER);
return true;
}
public boolean clearLogcatArea() {
logcatPane.setText("");
return true;
}
public boolean init(ADBDevice device, String pid) {
this.pid = Integer.parseInt(pid);
try {
this.logcatController = new LogcatController(this, device);
this.procs = device.getProcessList();
if (!this.showLogcat()) {
debugPanel.log(NLS.str("logcat.error_fail_start"));
}
} catch (Exception e) {
this.ready = false;
LOG.error("Failed to start logcat", e);
return false;
}
this.ready = true;
return true;
}
private void toggleLogcat() {
if (Objects.equals(this.logcatController.getStatus(), "running")) {
this.logcatController.stopLogcat();
this.pauseButton.putValue(Action.SMALL_ICON, ICON_RUN);
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.start"));
} else if (Objects.equals(this.logcatController.getStatus(), "stopped")) {
this.logcatController.startLogcat();
this.pauseButton.putValue(Action.SMALL_ICON, ICON_PAUSE);
this.pauseButton.putValue(Action.NAME, NLS.str("logcat.pause"));
}
}
private void clearLogcat() {
boolean running = false;
if (Objects.equals(this.logcatController.getStatus(), "running")) {
this.logcatController.stopLogcat();
running = true;
}
this.logcatController.clearLogcat();
clearLogcatArea();
this.debugPanel.log(this.logcatController.getStatus());
if (running) {
this.logcatController.startLogcat();
}
}
public boolean isReady() {
return this.ready;
}
private boolean isAtBottom(JScrollBar scrollbar) {
BoundedRangeModel model = scrollbar.getModel();
return (model.getExtent() + model.getValue()) == model.getMaximum();
}
public void log(LogcatController.LogcatInfo logcatInfo) {
boolean atBottom = false;
int len = logcatPane.getDocument().getLength();
JScrollBar scrollbar = logcatScroll.getVerticalScrollBar();
if (isAtBottom(scrollbar)) {
atBottom = true;
}
StringBuilder sb = new StringBuilder();
sb.append(" > ")
.append(logcatInfo.getTimestamp())
.append(" [pid: ")
.append(logcatInfo.getPid())
.append("] ")
.append(logcatInfo.getMsgTypeString())
.append(": ")
.append(logcatInfo.getMsg())
.append("\n");
try {
switch (logcatInfo.getMsgType()) {
case 0: // Unknown
break;
case 1: // Default
logcatPane.getDocument().insertString(len, sb.toString(), defaultAset);
break;
case 2: // Verbose
logcatPane.getDocument().insertString(len, sb.toString(), verboseAset);
break;
case 3: // Debug
logcatPane.getDocument().insertString(len, sb.toString(), debugAset);
break;
case 4: // Info
logcatPane.getDocument().insertString(len, sb.toString(), infoAset);
break;
case 5: // Warn
logcatPane.getDocument().insertString(len, sb.toString(), warningAset);
break;
case 6: // Error
logcatPane.getDocument().insertString(len, sb.toString(), errorAset);
break;
case 7: // Fatal
logcatPane.getDocument().insertString(len, sb.toString(), fatalAset);
break;
case 8: // Silent
logcatPane.getDocument().insertString(len, sb.toString(), silentAset);
break;
default:
logcatPane.getDocument().insertString(len, sb.toString(), null);
break;
}
} catch (Exception e) {
LOG.error("Failed to write logcat message", e);
}
if (atBottom) {
EventQueue.invokeLater(() -> scrollbar.setValue(scrollbar.getMaximum()));
}
}
public void exit() {
logcatController.exit();
clearLogcatArea();
this.logcatController.clearEvents();
}
class CheckCombo implements ActionListener {
private final String[] ids;
private final int type;
private final String label;
private final Integer[] index;
private JComboBox<CheckComboStore> combo;
public CheckCombo(String label, int type, Integer[] index, String[] ids) {
this.ids = ids;
this.type = type;
this.label = label;
this.index = index;
}
public void actionPerformed(ActionEvent e) {
JComboBox cb = (JComboBox) e.getSource();
CheckComboStore store = (CheckComboStore) cb.getSelectedItem();
CheckComboRenderer ccr = (CheckComboRenderer) cb.getRenderer();
store.state = !store.state;
ccr.checkBox.setSelected(store.state);
switch (this.type) {
case 1: // process
logcatController.getFilter().togglePid(store.index, store.state);
logcatController.reload();
break;
case 2: // label
logcatController.getFilter().toggleMsgType((byte) store.index, store.state);
logcatController.reload();
break;
default:
LOG.error("Invalid Logcat Filter Type");
break;
}
}
public JPanel getContent() {
JLabel label = NodeLabel.noHtml(this.label + ": ");
CheckComboStore[] stores = new CheckComboStore[ids.length];
for (int j = 0; j < ids.length; j++) {
stores[j] = new CheckComboStore(index[j], ids[j], Boolean.TRUE);
}
combo = new JComboBox<>(stores);
combo.setRenderer(new CheckComboRenderer());
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.weightx = 0;
c.gridwidth = 1;
c.insets = new Insets(0, 1, 0, 1);
panel.add(label, c);
c.weightx = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.WEST;
c.insets = new Insets(0, 1, 0, 1);
panel.add(combo, c);
combo.addActionListener(this);
combo.addMouseListener(new FilterClickListener(this));
return panel;
}
public void toggleAll(boolean checked) {
for (int i = 0; i < combo.getItemCount(); i++) {
CheckComboStore ccs = combo.getItemAt(i);
ccs.state = checked;
switch (type) {
case 1: // process
logcatController.getFilter().togglePid(ccs.index, checked);
break;
case 2: // level
logcatController.getFilter().toggleMsgType((byte) ccs.index, checked);
break;
default:
LOG.error("Invalid Logcat Toggle Filter Encountered");
break;
}
}
logcatController.reload();
}
public void selectAllBut(int ind) {
for (int i = 0; i < combo.getItemCount(); i++) {
CheckComboStore ccs = combo.getItemAt(i);
if (i != ind) {
ccs.state = false;
} else {
ccs.state = true;
}
switch (type) {
case 1: // process
logcatController.getFilter().togglePid(ccs.index, ccs.state);
break;
case 2: // level
logcatController.getFilter().toggleMsgType((byte) ccs.index, ccs.state);
break;
default:
LOG.error("Invalid Logcat selectAllBut filter encountered");
break;
}
}
logcatController.reload();
}
}
class CheckComboRenderer implements ListCellRenderer {
JCheckBox checkBox;
ArrayList<JCheckBox> boxes = new ArrayList<>();
public CheckComboRenderer() {
checkBox = new JCheckBox();
}
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
CheckComboStore store = (CheckComboStore) value;
checkBox.setText(store.id);
checkBox.setSelected(store.state);
boxes.add(checkBox);
return checkBox;
}
}
static class CheckComboStore {
String id;
Boolean state;
int index;
public CheckComboStore(int index, String id, Boolean state) {
this.id = id;
this.state = state;
this.index = index;
}
}
class FilterClickListener extends MouseAdapter {
CheckCombo combo;
public FilterClickListener(CheckCombo combo) {
this.combo = combo;
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
doPop(e);
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
doPop(e);
}
}
private void doPop(MouseEvent e) {
FilterPopup menu = new FilterPopup(combo);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
class FilterPopup extends JPopupMenu {
CheckCombo combo;
JMenuItem selectAll;
JMenuItem unselectAll;
JMenuItem selectAttached;
public FilterPopup(CheckCombo combo) {
this.combo = combo;
selectAll = new JMenuItem(NLS.str("logcat.select_all"));
selectAll.addActionListener(actionEvent -> combo.toggleAll(true));
unselectAll = new JMenuItem(NLS.str("logcat.unselect_all"));
unselectAll.addActionListener(actionEvent -> combo.toggleAll(false));
if (combo.type == 1) {
selectAttached = new JMenuItem(NLS.str("logcat.select_attached"));
selectAttached.addActionListener(actionEvent -> combo.selectAllBut(pids.indexOf(pid)));
add(selectAttached);
}
add(selectAll);
add(unselectAll);
}
}
}
@@ -0,0 +1,47 @@
package jadx.gui.utils.ui;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import jadx.gui.treemodel.JNode;
public class NodeLabel extends JLabel {
public static NodeLabel longName(JNode node) {
NodeLabel label = new NodeLabel(node.makeLongStringHtml(), node.disableHtml());
label.setIcon(node.getIcon());
label.setHorizontalAlignment(SwingConstants.LEFT);
return label;
}
public static NodeLabel noHtml(String label) {
return new NodeLabel(label, true);
}
public static void disableHtml(JLabel label, boolean disable) {
label.putClientProperty("html.disable", disable);
}
private boolean htmlDisabled = false;
public NodeLabel() {
disableHtml(true);
}
public NodeLabel(String label) {
disableHtml(true);
setText(label);
}
public NodeLabel(String label, boolean disableHtml) {
disableHtml(disableHtml);
setText(label);
}
public void disableHtml(boolean disable) {
if (htmlDisabled != disable) {
htmlDisabled = disable;
disableHtml(this, disable);
}
}
}
@@ -194,7 +194,8 @@ preferences.rename=Umbenennen
preferences.rename_case=System unterscheidet zwischen Groß/Kleinschreibung
preferences.rename_valid=Ist eine gültige Kennung
preferences.rename_printable=Ist druckbar
preferences.search_res_title=Ressourcen durchsuchen
preferences.search_group_title=Ressourcen durchsuchen
#preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Dateierweiterungen (z.B. .xml|.html), * bedeutet alle
preferences.res_skip_file=Dateien überspringen (MB)
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Wechsel zu 0
debugger.popup_change_to_one=Wechsel zu 1
debugger.popup_copy_value=Wert kopieren
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=Wert
set_value_dialog.btn_set=Wert einstellen
set_value_dialog.title=Wert Einstellen
@@ -12,7 +12,7 @@ menu.alwaysSelectOpened=Always Select Opened File/Class
menu.navigation=Navigation
menu.text_search=Text search
menu.class_search=Class search
menu.comment_search=Comment search
menu.comment_search=Comment searchF
menu.tools=Tools
menu.deobfuscation=Deobfuscation
menu.log=Log Viewer
@@ -194,9 +194,10 @@ preferences.rename=Rename identifiers
preferences.rename_case=To fix case sensitivity issues
preferences.rename_valid=To make them valid
preferences.rename_printable=To make printable
preferences.search_res_title=Search Resource
preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all
preferences.res_skip_file=Skip files exceed (MB)
preferences.search_group_title=Search
preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
preferences.res_skip_file=Skip resources files if larger (MB)
msg.open_file=Please open file
msg.saving_sources=Saving sources
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Change to 0
debugger.popup_change_to_one=Change to 1
debugger.popup_copy_value=Copy Value
logcat.pause=Pause Logcat
logcat.start=Resume Logcat
logcat.clear=Clear Logcat
logcat.error_fail_start=Failed to start logcat
logcat.process=Process
logcat.level=Level
logcat.default=Default
logcat.verbose=Verbose
logcat.debug=Debug
logcat.info=Info
logcat.warn=Warn
logcat.error=Error
logcat.fatal=Fatal
logcat.silent=Silent
logcat.logcat=Logcat
logcat.select_attached=Select Attached
logcat.select_all=Select All
logcat.unselect_all=Unselect All
set_value_dialog.label_value=Value
set_value_dialog.btn_set=Set Value
set_value_dialog.title=Set Value
@@ -194,9 +194,10 @@ preferences.reset_title=Reestablecer preferencias
#preferences.rename_case=
#preferences.rename_valid=
#preferences.rename_printable=
#preferences.search_res_title=
#preferences.res_file_ext=
#preferences.res_skip_file=
#preferences.search_group_title=Search
#preferences.search_results_per_page=Results per page (0 - no limit)
#preferences.res_file_ext=Resource files extensions ('xml|html', * for all)
#preferences.res_skip_file=Skip resources files if larger (MB)
msg.open_file=Por favor, abra un archivo
msg.saving_sources=Guardando fuente
@@ -287,6 +288,26 @@ certificate.serialPubKeyY=Y
#debugger.popup_change_to_one=Change to 1
#debugger.popup_copy_value=Copy Value
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
#set_value_dialog.label_value=Value
#set_value_dialog.btn_set=Set Value
#set_value_dialog.title=Set Value
@@ -23,31 +23,31 @@ menu.update_label=새 버전 %s 이(가) 존재합니다!
file.open_action=파일 열기 ...
file.add_files_action=파일 추가
file.open_title=파일 열기
#file.open_project=Open project
file.open_project=프로젝트 열기
file.new_project=새 프로젝트
file.save_project=프로젝트 저장
file.save_project_as=다른 이름으로 프로젝트 저장...
#file.reload=Reload files
#file.live_reload=Live reload
#file.live_reload_desc=Auto reload files on changes
#file.export_mappings_as=
file.reload=파일 다시 로드
file.live_reload=라이브 로드
file.live_reload_desc=파일 내용 변경 시 자동으로 다시 로드
file.export_mappings_as=다른 이름으로 매핑 내보내기...
file.save_all=모두 저장
file.export_gradle=Gradle 프로젝트로 저장
file.save_all_msg=디컴파일된 소스를 저장할 디렉토리 선택
file.exit=나가기
#start_page.title=Start page
#start_page.start=Start
#start_page.recent=Recent projects
start_page.title=페이지 시작
start_page.start=시작
start_page.recent=최근 프로젝트
tree.sources_title=소스코드
tree.resources_title=리소스
tree.loading=로딩중...
progress.load=로딩중
#progress.export_mappings=
progress.export_mappings=매핑 내보내는 중
progress.decompile=디컴파일 중
#progress.canceling=Canceling
progress.canceling=취소 중
error_dialog.title=오류
@@ -89,16 +89,16 @@ common_dialog.add=추가
common_dialog.update=업데이트
common_dialog.remove=삭제
#file_dialog.supported_files=Supported files
#file_dialog.load_dir_title=Load directory
#file_dialog.load_dir_confirm=Load all files from directory?
file_dialog.supported_files=지원되는 파일
file_dialog.load_dir_title=디렉토리 불러오기
file_dialog.load_dir_confirm=디렉토리에서 모든 파일을 불러오시겠습니까?
search_dialog.open=열기
search_dialog.cancel=취소
search_dialog.open_by_name=텍스트 검색 :
#search_dialog.search_button=Search
#search_dialog.search_history=Search history
#search_dialog.auto_search=Auto search
search_dialog.search_button=검색
search_dialog.search_history=검색 기록
search_dialog.auto_search=자동 검색
search_dialog.search_in=정의 검색 :
search_dialog.class=클래스
search_dialog.method=메소드
@@ -106,14 +106,14 @@ search_dialog.field=필드
search_dialog.code=코드
search_dialog.options=옵션 검색:
search_dialog.ignorecase=대소문자 구분 안함
#search_dialog.load_more=Load more
#search_dialog.load_all=Load all
#search_dialog.stop=Stop
#search_dialog.results_incomplete=Found %d+
#search_dialog.results_complete=Found %d (complete)
search_dialog.load_more=더보기
search_dialog.load_all=모두 로드
search_dialog.stop=정지
search_dialog.results_incomplete=%d+개 찾음
search_dialog.results_complete=%d개 찾음 (검색 완료)
search_dialog.col_node=노드
search_dialog.col_code=코드
#search_dialog.sort_results=Sort results
search_dialog.sort_results=결과 정렬
search_dialog.regex=정규식
search_dialog.active_tab=열려 있는 탭에서만 검색
search_dialog.comments=주석
@@ -138,28 +138,28 @@ preferences.title=설정
preferences.deobfuscation=난독화 해제
preferences.appearance=외관
preferences.decompile=디컴파일
#preferences.plugins=Plugins
preferences.plugins=플러그인
preferences.project=프로젝트
preferences.other=기타
preferences.language=언어
preferences.lineNumbersMode=편집기 줄 번호 모드
#preferences.jumpOnDoubleClick=Enable jump on double click
preferences.jumpOnDoubleClick=더블 클릭 시 점프 활성화
preferences.check_for_updates=시작시 업데이트 확인
#preferences.useDx=Use dx/d8 to convert java bytecode
#preferences.decompilationMode=Decompilation mode
#preferences.codeCacheMode=Code cache mode
preferences.useDx=dx/d8을 사용하여 Java 바이트 코드 변환
preferences.decompilationMode=디컴파일 모드
preferences.codeCacheMode=코드 캐시 모드
preferences.showInconsistentCode=디컴파일 안된 코드 표시
preferences.escapeUnicode=유니코드 이스케이프
preferences.replaceConsts=상수 바꾸기
preferences.respectBytecodeAccessModifiers=바이트코드 액세스 수정자 존중
preferences.useImports=import 문 사용
#preferences.useDebugInfo=Use debug info
preferences.useDebugInfo=디버그 정보 사용
preferences.inlineAnonymous=인라인 익명 클래스
preferences.inlineMethods=인라인 메서드
#preferences.extractFinally=Extract finally block
preferences.extractFinally=finally 블록 추출
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
#preferences.useKotlinMethodsForVarNames=Use kotlin methods for variables rename
preferences.useKotlinMethodsForVarNames=변수 이름 바꾸기에 kotlin 메서드 사용
preferences.commentsLevel=코드 주석 수준
preferences.autoSave=자동 저장
preferences.threads=처리 스레드 수
@@ -177,12 +177,12 @@ preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경
preferences.select_smali_font=변경
preferences.deobfuscation_on=난독 해제 활성화
#preferences.deobfuscation_map_file_mode=Map file handle mode
preferences.deobfuscation_map_file_mode=맵 파일 처리 모드
preferences.deobfuscation_min_len=최소 이름 길이
preferences.deobfuscation_max_len=최대 이름 길이
preferences.deobfuscation_source_alias=소스 파일 이름을 클래스 이름 별칭으로 사용
preferences.deobfuscation_kotlin_metadata=클래스 및 패키지 이름에 대한 Kotlin 메타 데이터 파싱
#preferences.deobfuscation_res_name_source=Better resources name source
preferences.deobfuscation_res_name_source=더 나은 리소스 이름 소스
preferences.save=저장
preferences.cancel=취소
preferences.reset=재설정
@@ -194,7 +194,8 @@ preferences.rename=이름 바꾸기
preferences.rename_case=시스템 대소문자 구분
preferences.rename_valid=유효한 식별자로 바꾸기
preferences.rename_printable=출력 가능하게 바꾸기
preferences.search_res_title=리소스 검색
preferences.search_group_title=리소스 검색
#preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=파일 확장자 (예: .xml|.html) (* 은 전체를 의미)
preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB)
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=0으로 변경
debugger.popup_change_to_one=1로 변경
debugger.popup_copy_value=값 복사
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=
set_value_dialog.btn_set=값 설정
set_value_dialog.title=값 설정
@@ -23,7 +23,7 @@ menu.update_label=Nova versão %s disponível!
file.open_action=Abrir arquivos...
file.add_files_action=Adicionar arquivos
file.open_title=Abrir arquivo
#file.open_project=Open project
file.open_project=Abrir projeto
file.new_project=Novo projeto
file.save_project=Salvar projeto
file.save_project_as=Salvar projeto como...
@@ -36,9 +36,9 @@ file.export_gradle=Salvar como um projeto gradle
file.save_all_msg=Selecionar diretório para salvar arquivos descompilados
file.exit=Sair
#start_page.title=Start page
#start_page.start=Start
#start_page.recent=Recent projects
start_page.title=Página inicial
start_page.start=Começar
start_page.recent=Projetos recentes
tree.sources_title=Código fonte
tree.resources_title=Recursos
@@ -95,10 +95,10 @@ file_dialog.load_dir_confirm=Carregar todos arquivos do diretório?
search_dialog.open=Abrir
search_dialog.cancel=Cancelar
search_dialog.open_by_name=Buscar por text:
#search_dialog.search_button=Search
#search_dialog.search_history=Search history
#search_dialog.auto_search=Auto search
search_dialog.open_by_name=Buscar por texto:
search_dialog.search_button=Buscar
search_dialog.search_history=Buscar histórico
search_dialog.auto_search=Busca automática
search_dialog.search_in=Buscar definições de:
search_dialog.class=Classe
search_dialog.method=Método
@@ -108,13 +108,13 @@ search_dialog.options=Opções de busca:
search_dialog.ignorecase=Não diferencia maiúsculas de minúsculas
search_dialog.load_more=Carregar mais
search_dialog.load_all=Carregar todas
#search_dialog.stop=Stop
search_dialog.stop=Parar
search_dialog.results_incomplete=Encontradas %d+
search_dialog.results_complete=Encontradas %d (complete)
search_dialog.results_complete=Encontradas %d (completos)
search_dialog.col_node=
search_dialog.col_code=Código
#search_dialog.sort_results=Sort results
search_dialog.regex=Regex
search_dialog.sort_results=Ordenar resultados
search_dialog.regex=Expressão regular
search_dialog.active_tab=Apenas abas ativas
search_dialog.comments=Comentários
search_dialog.resource=Recursos
@@ -194,7 +194,8 @@ preferences.rename=Renomear identificadores
preferences.rename_case=Corrigir problemas de capitalização (case sensitivity)
preferences.rename_valid=Deixá-las válidas
preferences.rename_printable=Deixá-las imprimíveis (printable)
preferences.search_res_title=Buscar recursos
preferences.search_group_title=Buscar recursos
#preferences.search_results_per_page=Results per page (0 - no limit)
preferences.res_file_ext=Extensões de arquivos (ex: .xml|.html), * significa todas
preferences.res_skip_file=Pular arquivos excedidos
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=Alterar para 0
debugger.popup_change_to_one=Alterar para 1
debugger.popup_copy_value=Copiar valor
#logcat.pause=Pause Logcat
#logcat.start=Resume Logcat
#logcat.clear=Clear Logcat
#logcat.error_fail_start=Failed to start logcat
#logcat.process=Process
#logcat.level=Level
#logcat.default=Default
#logcat.verbose=Verbose
#logcat.debug=Debug
#logcat.info=Info
#logcat.warn=Warn
#logcat.error=Error
#logcat.fatal=Fatal
#logcat.silent=Silent
#logcat.logcat=Logcat
#logcat.select_attached=Select Attached
#logcat.select_all=Select All
#logcat.unselect_all=Unselect All
set_value_dialog.label_value=Valor
set_value_dialog.btn_set=Definir Valor
set_value_dialog.title=Definir Valor
@@ -194,7 +194,8 @@ preferences.rename=重命名标识符
preferences.rename_case=需要标识符能区分大小写
preferences.rename_valid=需要标识符能符合规范
preferences.rename_printable=需要标识符可正常显示
preferences.search_res_title=搜索资源
preferences.search_group_title=搜索资源
preferences.search_results_per_page=每页结果数(0 - 无限制)
preferences.res_file_ext=文件扩展名 (e.g. .xml|.html)* 表示所有
preferences.res_skip_file=跳过文件大小(MB)
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=修改为0
debugger.popup_change_to_one=修改为1
debugger.popup_copy_value=复制值
logcat.pause=暂停 Logcat
logcat.start=恢复 Logcat
logcat.clear=清空 Logcat
logcat.error_fail_start=无法启动Logcat
logcat.process=进程
logcat.level=级别
logcat.default=默认
logcat.verbose=Verbose
logcat.debug=Debug
logcat.info=Info
logcat.warn=Warn
logcat.error=Error
logcat.fatal=Fatal
logcat.silent=Silent
logcat.logcat=Logcat
logcat.select_attached=选择附加
logcat.select_all=选择所有
logcat.unselect_all=反选所有
set_value_dialog.label_value=
set_value_dialog.btn_set=修改
set_value_dialog.title=设置新值
@@ -23,7 +23,7 @@ menu.update_label=新版本 %s 可供下載!
file.open_action=開啟檔案...
file.add_files_action=新增檔案
file.open_title=開啟檔案
#file.open_project=Open project
file.open_project=開啟專案
file.new_project=新建專案
file.save_project=儲存專案
file.save_project_as=另存專案...
@@ -36,9 +36,9 @@ file.export_gradle=另存為 gradle 專案
file.save_all_msg=選擇儲存反編譯原始碼的路徑
file.exit=離開
#start_page.title=Start page
#start_page.start=Start
#start_page.recent=Recent projects
start_page.title=開始頁面
start_page.start=開始
start_page.recent=近期專案
tree.sources_title=原始碼
tree.resources_title=資源
@@ -96,9 +96,9 @@ file_dialog.load_dir_confirm=要載入目錄中的所有檔案嗎?
search_dialog.open=開啟
search_dialog.cancel=取消
search_dialog.open_by_name=搜尋文字:
#search_dialog.search_button=Search
#search_dialog.search_history=Search history
#search_dialog.auto_search=Auto search
search_dialog.search_button=搜尋
search_dialog.search_history=搜尋記錄
search_dialog.auto_search=自動搜尋
search_dialog.search_in=搜尋定義:
search_dialog.class=類別
search_dialog.method=方法
@@ -108,12 +108,12 @@ search_dialog.options=搜尋選項:
search_dialog.ignorecase=不區分大小寫
search_dialog.load_more=載入更多
search_dialog.load_all=載入全部
#search_dialog.stop=Stop
search_dialog.stop=停止
search_dialog.results_incomplete=找到 %d+
search_dialog.results_complete=找到 %d (完整)
search_dialog.col_node=
search_dialog.col_code=程式碼
#search_dialog.sort_results=Sort results
search_dialog.sort_results=排序結果
search_dialog.regex=Regex
search_dialog.active_tab=僅使用中分頁
search_dialog.comments=註解
@@ -156,7 +156,7 @@ preferences.useImports=使用 import 陳述式
preferences.useDebugInfo=使用除錯資訊
preferences.inlineAnonymous=內嵌匿名類別
preferences.inlineMethods=內嵌方式
#preferences.extractFinally=Extract finally block
preferences.extractFinally=擷取 finally 區塊
preferences.fsCaseSensitive=檔案系統區分大小寫
preferences.skipResourcesDecode=不要為資源解碼
preferences.useKotlinMethodsForVarNames=使用 Kotlin 方法來為變數重新命名
@@ -194,7 +194,8 @@ preferences.rename=重新命名識別碼
preferences.rename_case=以修復區分大小寫問題
preferences.rename_valid=以使其有效
preferences.rename_printable=以使其可列印
preferences.search_res_title=搜尋資源
preferences.search_group_title=搜尋資源
preferences.search_results_per_page=每頁的搜尋結果數 (0 - 無限制)
preferences.res_file_ext=副檔名 (e.g. .xml|.html), * 表示全部
preferences.res_skip_file=略過大於此值的檔案 (MB)
@@ -287,6 +288,26 @@ debugger.popup_change_to_zero=修改為 0
debugger.popup_change_to_one=修改為 1
debugger.popup_copy_value=複製數值
logcat.pause=暫停 Logcat
logcat.start=繼續 Logcat
logcat.clear=清除 Logcat
logcat.error_fail_start=無法啟動 Logcat
logcat.process=程序
logcat.level=等級
logcat.default=預設
logcat.verbose=詳細資訊
logcat.debug=偵錯
logcat.info=資訊
logcat.warn=警告
logcat.error=錯誤
logcat.fatal=嚴重錯誤
logcat.silent=靜音
logcat.logcat=Logcat
logcat.select_attached=選取附件
logcat.select_all=全選
logcat.unselect_all=取消全選
set_value_dialog.label_value=數值
set_value_dialog.btn_set=設置數值
set_value_dialog.title=設置數值
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0002 3H12.0002H13.0002V4H12.0002V13L11.0002 14H4.00024L3.00024 13V4H2.00024V3H5.00024V2C5.00024 1.73478 5.10555 1.48038 5.29309 1.29285C5.48063 1.10531 5.73503 1 6.00024 1H9.00024C9.26546 1 9.51986 1.10531 9.7074 1.29285C9.89493 1.48038 10.0002 1.73478 10.0002 2V3ZM9.00024 2H6.00024V3H9.00024V2ZM4.00024 13H11.0002V4H4.00024V13ZM6.00024 5H5.00024V12H6.00024V5ZM7.00024 5H8.00024V12H7.00024V5ZM9.00024 5H10.0002V12H9.00024V5Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 600 B

+2 -2
View File
@@ -7,7 +7,7 @@ dependencies {
implementation(project(":jadx-plugins:jadx-dex-input"))
implementation('com.jakewharton.android.repackaged:dalvik-dx:11.0.0_r3')
implementation('com.android.tools:r8:3.3.28')
implementation('com.android.tools:r8:3.3.75')
implementation 'org.ow2.asm:asm:9.3'
implementation 'org.ow2.asm:asm:9.4'
}
@@ -53,10 +53,10 @@ public class ZipSecurity {
if (isInSubDirectoryInternal(currentPath, canonical)) {
return true;
}
LOG.error("Path traversal attack detected, invalid name: {}", entryName);
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
return false;
} catch (Exception e) {
LOG.error("Path traversal attack detected, invalid name: {}", entryName);
LOG.error("Invalid file name or path traversal attack detected: {}", entryName);
return false;
}
}