Merge branch 'master' into type-inference-wip
This commit is contained in:
+17
-31
@@ -12,8 +12,7 @@ before_install:
|
||||
- chmod +x gradlew
|
||||
|
||||
# override install to skip 'gradle assemble'
|
||||
install:
|
||||
- true
|
||||
install: true
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -21,34 +20,21 @@ env:
|
||||
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
|
||||
matrix:
|
||||
jdk:
|
||||
- openjdk8
|
||||
- oraclejdk8
|
||||
- openjdk11
|
||||
|
||||
script: ./gradlew clean build
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- env: JDK=oracle-8
|
||||
jdk: oraclejdk8
|
||||
- env: JDK=openjdk11
|
||||
jdk: openjdk11
|
||||
- stage: deploy-unstable
|
||||
jdk: openjdk8
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
script:
|
||||
- java -version
|
||||
- ./gradlew clean build
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
tags: false
|
||||
condition: $JDK = oracle-8
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: release
|
||||
tags: false
|
||||
condition: $JDK = oracle-8
|
||||
script: bash scripts/travis-release.sh
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- skylot@gmail.com
|
||||
- stage: deploy-release
|
||||
jdk: openjdk8
|
||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-release.sh
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'org.sonarqube' version '2.7'
|
||||
id 'com.github.ben-manes.versions' version '0.20.0'
|
||||
id 'com.github.ben-manes.versions' version '0.21.0'
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -35,12 +35,12 @@ allprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
compile 'org.slf4j:slf4j-api:1.7.26'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:2.1'
|
||||
testCompile 'org.mockito:mockito-core:2.23.4'
|
||||
testCompile 'org.mockito:mockito-core:2.25.0'
|
||||
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -5,13 +5,17 @@ dependencies {
|
||||
|
||||
compile files('lib/dx-1.16.jar')
|
||||
compile 'commons-io:commons-io:2.6'
|
||||
compile 'org.ow2.asm:asm:7.0'
|
||||
compile 'org.jetbrains:annotations:16.0.3'
|
||||
compile 'uk.com.robust-it:cloning:1.9.11'
|
||||
compile 'org.ow2.asm:asm:7.1'
|
||||
compile 'org.jetbrains:annotations:17.0.0'
|
||||
compile 'uk.com.robust-it:cloning:1.9.12'
|
||||
|
||||
testCompile 'org.smali:smali:2.2.5'
|
||||
testCompile 'org.smali:baksmali:2.2.5'
|
||||
testCompile 'org.smali:smali:2.2.6'
|
||||
testCompile 'org.smali:baksmali:2.2.6'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
|
||||
// update dependency in smali
|
||||
testCompile 'com.google.guava:guava:27.1-jre'
|
||||
testCompile 'com.beust:jcommander:1.74'
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -59,7 +58,6 @@ public final class JadxDecompiler {
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private CodeGen codeGen;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
@@ -97,7 +95,6 @@ public final class JadxDecompiler {
|
||||
|
||||
void init() {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
this.codeGen = new CodeGen();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -106,7 +103,6 @@ public final class JadxDecompiler {
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
@@ -215,9 +211,11 @@ public final class JadxDecompiler {
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -289,7 +287,7 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
ProcessClass.process(cls, passes, codeGen);
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
|
||||
@@ -2,8 +2,6 @@ package jadx.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
@@ -19,8 +17,8 @@ public final class ProcessClass {
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
|
||||
if (!generateCode && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
}
|
||||
synchronized (getSyncObj(cls)) {
|
||||
@@ -33,9 +31,9 @@ public final class ProcessClass {
|
||||
}
|
||||
cls.setState(PROCESSED);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
if (cls.getState() == PROCESSED && generateCode) {
|
||||
processDependencies(cls, passes);
|
||||
codeGen.visit(cls);
|
||||
CodeGen.generate(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
@@ -48,6 +46,6 @@ public final class ProcessClass {
|
||||
}
|
||||
|
||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ClassGen {
|
||||
imports.clear();
|
||||
}
|
||||
clsCode.add(clsBody);
|
||||
return clsCode;
|
||||
return clsCode.finish();
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public class CodeGen {
|
||||
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
public static void generate(ClassNode cls) throws CodegenException {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
cls.setCode(CodeWriter.EMPTY);
|
||||
} else {
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
cls.setCode(clsGen.makeClass());
|
||||
}
|
||||
}
|
||||
|
||||
private CodeGen() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ public class CodeWriter {
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
@@ -250,7 +252,7 @@ public class CodeWriter {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
public CodeWriter finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
@@ -266,11 +268,12 @@ public class CodeWriter {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.substring(0, len).equals(NL)) {
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -549,7 +548,7 @@ public class InsnGen {
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -567,20 +566,14 @@ public class InsnGen {
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
// anonymous class construction
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
code.add("/* anonymous class already generated */");
|
||||
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
|
||||
return;
|
||||
}
|
||||
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
@@ -123,7 +124,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
accFlagsValue = cls.getAccessFlags();
|
||||
}
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
|
||||
markAnonymousClass(this);
|
||||
buildCache();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
|
||||
@@ -394,6 +395,29 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
&& getDefaultConstructor() != null;
|
||||
}
|
||||
|
||||
public boolean isLambdaCls() {
|
||||
return accessFlags.isSynthetic() && accessFlags.isFinal()
|
||||
&& clsInfo.getType().getObject().contains(".-$$Lambda$")
|
||||
&& countStaticFields() == 0;
|
||||
}
|
||||
|
||||
private int countStaticFields() {
|
||||
int c = 0;
|
||||
for (FieldNode field : fields) {
|
||||
if (field.getAccessFlags().isStatic()) {
|
||||
c++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
private static void markAnonymousClass(ClassNode cls) {
|
||||
if (cls.isAnonymous() || cls.isLambdaCls()) {
|
||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getClassInitMth() {
|
||||
return searchMethodByName("<clinit>()V");
|
||||
|
||||
@@ -327,4 +327,24 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
return INSN_CLONER.deepClone(this);
|
||||
}
|
||||
|
||||
public boolean canThrowException() {
|
||||
switch (getType()) {
|
||||
case RETURN:
|
||||
case IF:
|
||||
case GOTO:
|
||||
case MOVE:
|
||||
case MOVE_EXCEPTION:
|
||||
case NEG:
|
||||
case CONST:
|
||||
case CONST_STR:
|
||||
case CONST_CLASS:
|
||||
case CMP_L:
|
||||
case CMP_G:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,16 +329,24 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
int offset = aTry.getStartAddress();
|
||||
int end = offset + aTry.getInstructionCount() - 1;
|
||||
|
||||
InsnNode insn = insnByOffset[offset];
|
||||
insn.add(AFlag.TRY_ENTER);
|
||||
boolean tryBlockStarted = false;
|
||||
InsnNode insn = null;
|
||||
while (offset <= end && offset >= 0) {
|
||||
insn = insnByOffset[offset];
|
||||
catchBlock.addInsn(insn);
|
||||
if (insn != null) {
|
||||
if (tryBlockStarted) {
|
||||
catchBlock.addInsn(insn);
|
||||
} else if (insn.canThrowException()) {
|
||||
insn.add(AFlag.TRY_ENTER);
|
||||
catchBlock.addInsn(insn);
|
||||
tryBlockStarted = true;
|
||||
}
|
||||
}
|
||||
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
}
|
||||
if (insnByOffset[end] != null) {
|
||||
insnByOffset[end].add(AFlag.TRY_LEAVE);
|
||||
} else {
|
||||
} else if (insn != null) {
|
||||
insn.add(AFlag.TRY_LEAVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +51,10 @@ public class ClassModifier extends AbstractVisitor {
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
return false;
|
||||
}
|
||||
removeSyntheticFields(cls);
|
||||
cls.getMethods().forEach(mth -> removeSyntheticMethods(cls, mth));
|
||||
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
||||
|
||||
markAnonymousClass(cls);
|
||||
removeSyntheticFields(cls);
|
||||
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
|
||||
cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -69,29 +68,36 @@ public class ClassModifier extends AbstractVisitor {
|
||||
private void markAnonymousClass(ClassNode cls) {
|
||||
if (cls.isAnonymous()) {
|
||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove synthetic fields if type is outer class or class will be inlined (anonymous)
|
||||
*/
|
||||
private static void removeSyntheticFields(ClassNode cls) {
|
||||
if (!cls.getClassInfo().isInner() || cls.getAccessFlags().isStatic()) {
|
||||
if (cls.getAccessFlags().isStatic()) {
|
||||
return;
|
||||
}
|
||||
// remove fields if it is synthetic and type is a outer class
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
|
||||
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
|
||||
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
|
||||
ClassInfo parentClass = cls.getClassInfo().getParentClass();
|
||||
if (fieldsCls != null && parentClass.equals(fieldsCls.getClassInfo())) {
|
||||
int found = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
|
||||
found++;
|
||||
boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS);
|
||||
if (inline || cls.getClassInfo().isInner()) {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
|
||||
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
|
||||
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
|
||||
ClassInfo parentClass = cls.getClassInfo().getParentClass();
|
||||
if (fieldsCls != null
|
||||
&& (inline || parentClass.equals(fieldsCls.getClassInfo()))) {
|
||||
int found = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
field.addAttr(new FieldReplaceAttr(fieldsCls.getClassInfo()));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
field.addAttr(new FieldReplaceAttr(parentClass));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +143,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void removeSyntheticMethods(ClassNode cls, MethodNode mth) {
|
||||
private static void removeSyntheticMethods(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
@@ -145,6 +151,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
if (!af.isSynthetic()) {
|
||||
return;
|
||||
}
|
||||
ClassNode cls = mth.getParentClass();
|
||||
if (removeBridgeMethod(cls, mth)) {
|
||||
if (Consts.DEBUG) {
|
||||
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
|
||||
|
||||
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class DependencyCollector extends AbstractVisitor {
|
||||
@@ -41,6 +42,12 @@ public class DependencyCollector extends AbstractVisitor {
|
||||
}
|
||||
for (FieldNode fieldNode : cls.getFields()) {
|
||||
addDep(dex, depList, fieldNode.getType());
|
||||
|
||||
// process instructions from field init
|
||||
FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT);
|
||||
if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) {
|
||||
processInsn(dex, depList, fieldInitAttr.getInsn());
|
||||
}
|
||||
}
|
||||
// TODO: process annotations and generics
|
||||
for (MethodNode methodNode : cls.getMethods()) {
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
@@ -136,7 +138,10 @@ public class RegionUtils {
|
||||
return !notEmpty(container);
|
||||
}
|
||||
|
||||
public static boolean notEmpty(IContainer container) {
|
||||
public static boolean notEmpty(@Nullable IContainer container) {
|
||||
if (container == null) {
|
||||
return false;
|
||||
}
|
||||
if (container instanceof IBlock) {
|
||||
return !((IBlock) container).getInstructions().isEmpty();
|
||||
} else if (container instanceof IRegion) {
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
package jadx.core.utils.android;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -86,6 +91,7 @@ public class AndroidResourcesUtils {
|
||||
}
|
||||
|
||||
private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) {
|
||||
Map<Integer, FieldNode> resFieldsMap = fillResFieldsMap(resCls);
|
||||
Map<String, ClassNode> innerClsMap = new TreeMap<>();
|
||||
if (rClsExists) {
|
||||
for (ClassNode innerClass : resCls.getInnerClasses()) {
|
||||
@@ -93,18 +99,20 @@ public class AndroidResourcesUtils {
|
||||
}
|
||||
}
|
||||
for (ResourceEntry resource : resStorage.getResources()) {
|
||||
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
|
||||
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + name,
|
||||
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
|
||||
resCls.addInnerClass(newTypeCls);
|
||||
if (rClsExists) {
|
||||
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
|
||||
}
|
||||
return newTypeCls;
|
||||
});
|
||||
FieldNode rField = typeCls.searchFieldByName(resource.getKeyName());
|
||||
final String resTypeName = resource.getTypeName();
|
||||
ClassNode typeCls = innerClsMap.computeIfAbsent(
|
||||
resTypeName,
|
||||
name -> addClassForResType(resCls, rClsExists, name)
|
||||
);
|
||||
final String resName;
|
||||
if ("style".equals(resTypeName)) {
|
||||
resName = resource.getKeyName().replace('.', '_');
|
||||
} else {
|
||||
resName = resource.getKeyName();
|
||||
}
|
||||
FieldNode rField = typeCls.searchFieldByName(resName);
|
||||
if (rField == null) {
|
||||
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resource.getKeyName(), ArgType.INT);
|
||||
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT);
|
||||
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
|
||||
rField.addAttr(FieldInitAttr.constValue(resource.getId()));
|
||||
typeCls.getFields().add(rField);
|
||||
@@ -112,6 +120,42 @@ public class AndroidResourcesUtils {
|
||||
rField.addAttr(AType.COMMENTS, "added by JADX");
|
||||
}
|
||||
}
|
||||
FieldNode fieldNode = resFieldsMap.get(resource.getId());
|
||||
if (fieldNode != null
|
||||
&& !fieldNode.getName().equals(resName)
|
||||
&& NameMapper.isValidIdentifier(resName)) {
|
||||
fieldNode.getFieldInfo().setAlias(resName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) {
|
||||
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + typeName,
|
||||
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
|
||||
resCls.addInnerClass(newTypeCls);
|
||||
if (rClsExists) {
|
||||
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
|
||||
}
|
||||
return newTypeCls;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<Integer, FieldNode> fillResFieldsMap(ClassNode resCls) {
|
||||
Map<Integer, FieldNode> resFieldsMap = new HashMap<>();
|
||||
ConstStorage constStorage = resCls.root().getConstValues();
|
||||
Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
|
||||
for (Map.Entry<Object, FieldNode> entry : constFields.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
FieldNode field = entry.getValue();
|
||||
AccessInfo accessFlags = field.getAccessFlags();
|
||||
if (field.getType().equals(ArgType.INT)
|
||||
&& accessFlags.isStatic()
|
||||
&& accessFlags.isFinal()
|
||||
&& key instanceof Integer) {
|
||||
resFieldsMap.put((Integer) key, field);
|
||||
}
|
||||
}
|
||||
return resFieldsMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ public class InputFile {
|
||||
loadFromZip(".dex");
|
||||
return;
|
||||
}
|
||||
if (fileName.endsWith(".jar")) {
|
||||
// check if jar contains '.dex' files
|
||||
if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) {
|
||||
// check if jar/aar contains '.dex' files
|
||||
if (loadFromZip(".dex")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -40,7 +38,6 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
private static final boolean ATTR_NEW_LINE = false;
|
||||
|
||||
private final Map<Integer, String> styleMap = new HashMap<>();
|
||||
private final Map<Integer, FieldNode> localStyleMap = new HashMap<>();
|
||||
private final Map<Integer, String> resNames;
|
||||
private final Map<String, String> nsMap = new HashMap<>();
|
||||
private Set<String> nsMapGenerated;
|
||||
@@ -63,16 +60,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
this.rootNode = rootNode;
|
||||
try {
|
||||
readAndroidRStyleClass();
|
||||
// add application constants
|
||||
ConstStorage constStorage = rootNode.getConstValues();
|
||||
Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
|
||||
for (Map.Entry<Object, FieldNode> entry : constFields.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
FieldNode field = entry.getValue();
|
||||
if (field.getType().equals(ArgType.INT) && key instanceof Integer) {
|
||||
localStyleMap.put((Integer) key, field);
|
||||
}
|
||||
}
|
||||
resNames = constStorage.getResourcesNames();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("BinaryXMLParser init error", e);
|
||||
@@ -381,38 +369,27 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
|
||||
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData,
|
||||
String shortNsName, String attrName) {
|
||||
|
||||
if (attrValDataType == TYPE_REFERENCE) {
|
||||
// reference custom processing
|
||||
String name = styleMap.get(attrValData);
|
||||
if (name != null) {
|
||||
writer.add("@style/").add(name.replaceAll("_", "."));
|
||||
} else {
|
||||
FieldNode field = localStyleMap.get(attrValData);
|
||||
if (field != null) {
|
||||
String cls = field.getParentClass().getShortName().toLowerCase();
|
||||
String resName = resNames.get(attrValData);
|
||||
if (resName != null) {
|
||||
writer.add("@");
|
||||
if ("id".equals(cls)) {
|
||||
writer.add('+');
|
||||
if (resName.startsWith("id/")) {
|
||||
writer.add("+");
|
||||
}
|
||||
writer.add(cls).add("/").add(field.getName());
|
||||
writer.add(resName);
|
||||
} else {
|
||||
String resName = resNames.get(attrValData);
|
||||
resName = ValuesParser.getAndroidResMap().get(attrValData);
|
||||
if (resName != null) {
|
||||
writer.add("@");
|
||||
if (resName.startsWith("id/")) {
|
||||
writer.add("+");
|
||||
}
|
||||
writer.add(resName);
|
||||
writer.add("@android:").add(resName);
|
||||
} else if (attrValData == 0) {
|
||||
writer.add("@null");
|
||||
} else {
|
||||
resName = ValuesParser.getAndroidResMap().get(attrValData);
|
||||
if (resName != null) {
|
||||
writer.add("@android:").add(resName);
|
||||
} else if (attrValData == 0) {
|
||||
writer.add("@null");
|
||||
} else {
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
}
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void decompile(JadxDecompiler jadx, ClassNode cls) {
|
||||
List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
|
||||
ProcessClass.process(cls, passes, new CodeGen());
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) {
|
||||
@@ -154,7 +154,7 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
protected void generateClsCode(ClassNode cls) {
|
||||
try {
|
||||
new CodeGen().visit(cls);
|
||||
CodeGen.generate(cls);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
|
||||
@@ -6,10 +6,8 @@ import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureClassLoader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static javax.tools.JavaFileObject.Kind;
|
||||
|
||||
|
||||
@@ -15,12 +15,11 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JadxInternalAccess;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
@@ -99,15 +98,10 @@ public abstract class BaseExternalTest extends IntegrationTest {
|
||||
if (!decompile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ProcessClass.process(classNode, passes, new CodeGen());
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraversal.visit(visitor, classNode);
|
||||
}
|
||||
try {
|
||||
new CodeGen().visit(classNode);
|
||||
ProcessClass.process(classNode, passes, true);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Codegen failed", e);
|
||||
throw new JadxRuntimeException("Class process failed", e);
|
||||
}
|
||||
LOG.info("----------------------------------------------------------------");
|
||||
LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex());
|
||||
|
||||
-1
@@ -3,7 +3,6 @@ package jadx.tests.integration.inner;
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
|
||||
-4
@@ -3,10 +3,6 @@ package jadx.tests.integration.inner;
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestInnerClassSyntheticConstructor extends IntegrationTest {
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.junit.Test;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.tests.integration.trycatch;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.JadxMatchers.containsOne;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TestTryCatchStartOnMove extends SmaliTest {
|
||||
|
||||
// private static void test(String s) {
|
||||
// try {
|
||||
// call(s);
|
||||
// } catch (Exception unused) {
|
||||
// System.out.println("Failed call for " + s);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private static void call(String s) {
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryCatchStartOnMove");
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("try {"));
|
||||
assertThat(code, containsOne("} catch (Exception e) {"));
|
||||
assertThat(code, containsOne("System.out.println(\"Failed call for \" + str"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
.class public Ltrycatch/TestTryCatchStartOnMove;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
|
||||
# direct methods
|
||||
.method private static test(Ljava/lang/String;)V
|
||||
.registers 5
|
||||
|
||||
:try_start
|
||||
move v3, p0
|
||||
invoke-static {v3}, Ltrycatch/TestTryCatchStartOnMove;->call(Ljava/lang/String;)V
|
||||
:try_end
|
||||
.catch Ljava/lang/Exception; {:try_start .. :try_end} :catch
|
||||
|
||||
:goto_ret
|
||||
return-void
|
||||
|
||||
:catch
|
||||
move-exception v0
|
||||
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
new-instance v1, Ljava/lang/StringBuilder;
|
||||
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V
|
||||
const-string v2, "Failed call for "
|
||||
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
move-result-object v1
|
||||
invoke-virtual {v1, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
move-result-object v1
|
||||
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
|
||||
move-result-object v1
|
||||
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
goto :goto_ret
|
||||
.end method
|
||||
|
||||
|
||||
.method public constructor <init>()V
|
||||
.registers 1
|
||||
invoke-direct {p0}, Ljadx/tests/api/SmaliTest;-><init>()V
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
.method private static call(Ljava/lang/String;)V
|
||||
.registers 1
|
||||
return-void
|
||||
.end method
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'edu.sc.seis.launch4j' version '2.4.4'
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.3'
|
||||
id 'edu.sc.seis.launch4j' version '2.4.5'
|
||||
id 'com.github.johnrengelman.shadow' version '5.0.0'
|
||||
}
|
||||
|
||||
apply plugin: 'application'
|
||||
@@ -13,7 +13,8 @@ targetCompatibility = JavaVersion.VERSION_1_8
|
||||
dependencies {
|
||||
compile(project(":jadx-core"))
|
||||
compile(project(":jadx-cli"))
|
||||
compile 'com.fifesoft:rsyntaxtextarea:3.0.0'
|
||||
|
||||
compile 'com.fifesoft:rsyntaxtextarea:3.0.2'
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
compile files('libs/jfontchooser-1.0.5.jar')
|
||||
compile 'hu.kazocsaba:image-viewer:1.2.3'
|
||||
@@ -21,9 +22,9 @@ dependencies {
|
||||
compile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
compile 'org.apache.commons:commons-text:1.6'
|
||||
|
||||
compile 'io.reactivex.rxjava2:rxjava:2.2.5'
|
||||
compile "com.github.akarnokd:rxjava2-swing:0.3.3"
|
||||
compile 'com.android.tools.build:apksig:3.3.0'
|
||||
compile 'io.reactivex.rxjava2:rxjava:2.2.7'
|
||||
compile "com.github.akarnokd:rxjava2-swing:0.3.4"
|
||||
compile 'com.android.tools.build:apksig:3.3.2'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package jadx.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ProgressMonitor;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -78,14 +81,13 @@ public class JadxWrapper {
|
||||
*/
|
||||
public List<JavaClass> getIncludedClasses() {
|
||||
List<JavaClass> classList = decompiler.getClasses();
|
||||
String excludedPackages = settings.getExcludedPackages().trim();
|
||||
if (excludedPackages.length() == 0) {
|
||||
List<String> excludedPackages = getExcludedPackages();
|
||||
if (excludedPackages.isEmpty()) {
|
||||
return classList;
|
||||
}
|
||||
String[] excluded = excludedPackages.split("[ ]+");
|
||||
|
||||
return classList.stream().filter(cls -> {
|
||||
for (String exclude : excluded) {
|
||||
for (String exclude : excludedPackages) {
|
||||
if (cls.getFullName().startsWith(exclude)) {
|
||||
return false;
|
||||
}
|
||||
@@ -94,6 +96,23 @@ public class JadxWrapper {
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getExcludedPackages() {
|
||||
String excludedPackages = settings.getExcludedPackages().trim();
|
||||
return Arrays.asList(excludedPackages.split("[ ]+"));
|
||||
}
|
||||
|
||||
public void addExcludedPackage(String packageToExclude) {
|
||||
settings.setExcludedPackages(settings.getExcludedPackages() + ' ' + packageToExclude);
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
public void removeExcludedPackage(String packageToRemoveFromExclusion) {
|
||||
List<String> list = new ArrayList<>(getExcludedPackages());
|
||||
list.remove(packageToRemoveFromExclusion);
|
||||
settings.setExcludedPackages(String.join(" ", list));
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
return decompiler.getPackages();
|
||||
}
|
||||
|
||||
@@ -333,4 +333,5 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
settingsVersion = CURRENT_SETTINGS_VERSION;
|
||||
sync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -247,10 +247,14 @@ public class JadxSettingsWindow extends JDialog {
|
||||
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
|
||||
editExcludedPackages.addActionListener(event -> {
|
||||
|
||||
String oldExcludedPackages = settings.getExcludedPackages();
|
||||
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
|
||||
settings.getExcludedPackages());
|
||||
if (result != null) {
|
||||
settings.setExcludedPackages(result);
|
||||
if (!oldExcludedPackages.equals(result)) {
|
||||
needReload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
public class JPackage extends JNode implements Comparable<JPackage> {
|
||||
@@ -15,12 +18,16 @@ public class JPackage extends JNode implements Comparable<JPackage> {
|
||||
|
||||
private static final ImageIcon PACKAGE_ICON = Utils.openIcon("package_obj");
|
||||
|
||||
private final String fullName;
|
||||
private String name;
|
||||
private boolean enabled;
|
||||
private final List<JClass> classes;
|
||||
private final List<JPackage> innerPackages = new ArrayList<>(1);
|
||||
|
||||
public JPackage(JavaPackage pkg) {
|
||||
public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
|
||||
this.fullName = pkg.getName();
|
||||
this.name = pkg.getName();
|
||||
setEnabled(wrapper);
|
||||
List<JavaClass> javaClasses = pkg.getClasses();
|
||||
this.classes = new ArrayList<>(javaClasses.size());
|
||||
for (JavaClass javaClass : javaClasses) {
|
||||
@@ -29,20 +36,30 @@ public class JPackage extends JNode implements Comparable<JPackage> {
|
||||
update();
|
||||
}
|
||||
|
||||
public JPackage(String name) {
|
||||
public JPackage(String name, JadxWrapper wrapper) {
|
||||
this.fullName = name;
|
||||
this.name = name;
|
||||
setEnabled(wrapper);
|
||||
this.classes = new ArrayList<>(1);
|
||||
}
|
||||
|
||||
private void setEnabled(JadxWrapper wrapper) {
|
||||
List<String> excludedPackages = wrapper.getExcludedPackages();
|
||||
this.enabled = excludedPackages.isEmpty()
|
||||
|| excludedPackages.stream().filter(p -> !p.isEmpty()).noneMatch(p -> name.startsWith(p));
|
||||
}
|
||||
|
||||
public final void update() {
|
||||
removeAllChildren();
|
||||
for (JPackage pkg : innerPackages) {
|
||||
pkg.update();
|
||||
add(pkg);
|
||||
}
|
||||
for (JClass cls : classes) {
|
||||
cls.update();
|
||||
add(cls);
|
||||
if (isEnabled()) {
|
||||
for (JPackage pkg : innerPackages) {
|
||||
pkg.update();
|
||||
add(pkg);
|
||||
}
|
||||
for (JClass cls : classes) {
|
||||
cls.update();
|
||||
add(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +68,10 @@ public class JPackage extends JNode implements Comparable<JPackage> {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -108,4 +129,8 @@ public class JPackage extends JNode implements Comparable<JPackage> {
|
||||
public String makeLongString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.gui.treemodel;
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -68,6 +69,14 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
|
||||
}
|
||||
} else {
|
||||
removeAllChildren();
|
||||
|
||||
Comparator<JResource> typeComparator
|
||||
= (r1, r2) -> r1.type.ordinal() - r2.type.ordinal();
|
||||
Comparator<JResource> nameComparator
|
||||
= Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
files.sort(typeComparator.thenComparing(nameComparator));
|
||||
|
||||
for (JResource res : files) {
|
||||
res.update();
|
||||
add(res);
|
||||
|
||||
@@ -33,7 +33,7 @@ public class JSources extends JNode {
|
||||
removeAllChildren();
|
||||
if (flatPackages) {
|
||||
for (JavaPackage pkg : wrapper.getPackages()) {
|
||||
add(new JPackage(pkg));
|
||||
add(new JPackage(pkg, wrapper));
|
||||
}
|
||||
} else {
|
||||
// build packages hierarchy
|
||||
@@ -54,7 +54,7 @@ public class JSources extends JNode {
|
||||
List<JPackage> getHierarchyPackages(List<JavaPackage> packages) {
|
||||
Map<String, JPackage> pkgMap = new HashMap<>();
|
||||
for (JavaPackage pkg : packages) {
|
||||
addPackage(pkgMap, new JPackage(pkg));
|
||||
addPackage(pkgMap, new JPackage(pkg, wrapper));
|
||||
}
|
||||
// merge packages without classes
|
||||
boolean repeat;
|
||||
@@ -114,7 +114,7 @@ public class JSources extends JNode {
|
||||
pkg.setName(shortName);
|
||||
JPackage prevPkg = pkgs.get(prevPart);
|
||||
if (prevPkg == null) {
|
||||
prevPkg = new JPackage(prevPart);
|
||||
prevPkg = new JPackage(prevPart, wrapper);
|
||||
addPackage(pkgs, prevPkg);
|
||||
}
|
||||
prevPkg.getInnerPackages().add(pkg);
|
||||
|
||||
@@ -2,9 +2,11 @@ package jadx.gui.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.net.URL;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
class AboutDialog extends JDialog {
|
||||
private static final long serialVersionUID = 5763493590584039096L;
|
||||
@@ -16,7 +18,10 @@ class AboutDialog extends JDialog {
|
||||
public final void initUI() {
|
||||
Font font = new Font("Serif", Font.BOLD, 13);
|
||||
|
||||
JLabel name = new JLabel("jadx");
|
||||
URL logoURL = getClass().getResource("/logos/jadx-logo-48px.png");
|
||||
Icon logo = new ImageIcon(logoURL, "jadx logo");
|
||||
|
||||
JLabel name = new JLabel("jadx", logo, SwingConstants.CENTER);
|
||||
name.setFont(font);
|
||||
name.setAlignmentX(0.5f);
|
||||
|
||||
@@ -24,10 +29,24 @@ class AboutDialog extends JDialog {
|
||||
desc.setFont(font);
|
||||
desc.setAlignmentX(0.5f);
|
||||
|
||||
JLabel version = new JLabel("version: " + JadxDecompiler.getVersion());
|
||||
JLabel version = new JLabel("jadx version: " + JadxDecompiler.getVersion());
|
||||
version.setFont(font);
|
||||
version.setAlignmentX(0.5f);
|
||||
|
||||
String javaVm = System.getProperty("java.vm.name");
|
||||
String javaVer = System.getProperty("java.vm.version");
|
||||
|
||||
javaVm = javaVm == null ? "" : javaVm;
|
||||
|
||||
JLabel javaVmLabel = new JLabel("Java VM: " + javaVm);
|
||||
javaVmLabel.setFont(font);
|
||||
javaVmLabel.setAlignmentX(0.5f);
|
||||
|
||||
javaVer = javaVer == null ? "" : javaVer;
|
||||
JLabel javaVerLabel = new JLabel("Java version: " + javaVer);
|
||||
javaVerLabel.setFont(font);
|
||||
javaVerLabel.setAlignmentX(0.5f);
|
||||
|
||||
JPanel textPane = new JPanel();
|
||||
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||
textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS));
|
||||
@@ -38,6 +57,9 @@ class AboutDialog extends JDialog {
|
||||
textPane.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
textPane.add(version);
|
||||
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
|
||||
textPane.add(javaVmLabel);
|
||||
textPane.add(javaVerLabel);
|
||||
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
|
||||
|
||||
JButton close = new JButton(NLS.str("tabs.close"));
|
||||
close.addActionListener(event -> dispose());
|
||||
@@ -47,6 +69,8 @@ class AboutDialog extends JDialog {
|
||||
contentPane.add(textPane, BorderLayout.CENTER);
|
||||
contentPane.add(close, BorderLayout.PAGE_END);
|
||||
|
||||
Utils.setWindowIcons(this);
|
||||
|
||||
setModalityType(ModalityType.APPLICATION_MODAL);
|
||||
|
||||
setTitle(NLS.str("about_dialog.title"));
|
||||
|
||||
@@ -1,6 +1,47 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.DisplayMode;
|
||||
import java.awt.Font;
|
||||
import java.awt.GraphicsDevice;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.ProgressMonitor;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.MenuEvent;
|
||||
import javax.swing.event.MenuListener;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
@@ -12,22 +53,6 @@ import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.awt.*;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.dnd.DropTarget;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Theme;
|
||||
import org.slf4j.Logger;
|
||||
@@ -46,6 +71,7 @@ import jadx.gui.treemodel.JCertificate;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JLoadableNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
@@ -57,8 +83,6 @@ import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class MainWindow extends JFrame {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class);
|
||||
@@ -82,6 +106,7 @@ public class MainWindow extends JFrame {
|
||||
private static final ImageIcon ICON_PREF = Utils.openIcon("wrench");
|
||||
private static final ImageIcon ICON_DEOBF = Utils.openIcon("lock_edit");
|
||||
private static final ImageIcon ICON_LOG = Utils.openIcon("report");
|
||||
private static final ImageIcon ICON_JADX = Utils.openIcon("jadx-logo");
|
||||
|
||||
private final transient JadxWrapper wrapper;
|
||||
private final transient JadxSettings settings;
|
||||
@@ -116,16 +141,7 @@ public class MainWindow extends JFrame {
|
||||
registerBundledFonts();
|
||||
initUI();
|
||||
initMenuAndToolbar();
|
||||
setWindowIcons();
|
||||
}
|
||||
|
||||
private void setWindowIcons() {
|
||||
List<Image> icons = new ArrayList<>();
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-16px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-32px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-48px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo.png"));
|
||||
setIconImages(icons);
|
||||
Utils.setWindowIcons(this);
|
||||
loadSettings();
|
||||
checkForUpdate();
|
||||
}
|
||||
@@ -323,6 +339,14 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void treeRightClickAction(MouseEvent e) {
|
||||
Object obj = tree.getLastSelectedPathComponent();
|
||||
if (obj instanceof JPackage) {
|
||||
JPackagePopUp menu = new JPackagePopUp((JPackage) obj);
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
|
||||
private void syncWithEditor() {
|
||||
ContentPanel selectedContentPanel = tabbedPane.getSelectedCodePanel();
|
||||
if (selectedContentPanel == null) {
|
||||
@@ -462,7 +486,7 @@ public class MainWindow extends JFrame {
|
||||
logAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_L,
|
||||
KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK));
|
||||
|
||||
Action aboutAction = new AbstractAction(NLS.str("menu.about")) {
|
||||
Action aboutAction = new AbstractAction(NLS.str("menu.about"), ICON_JADX) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new AboutDialog().setVisible(true);
|
||||
@@ -582,7 +606,12 @@ public class MainWindow extends JFrame {
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
treeClickAction();
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
treeRightClickAction(e);
|
||||
}
|
||||
else {
|
||||
treeClickAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
tree.addKeyListener(new KeyAdapter() {
|
||||
@@ -602,6 +631,9 @@ public class MainWindow extends JFrame {
|
||||
if (value instanceof JNode) {
|
||||
setIcon(((JNode) value).getIcon());
|
||||
}
|
||||
if (value instanceof JPackage) {
|
||||
setEnabled(((JPackage) value).isEnabled());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
});
|
||||
@@ -748,4 +780,23 @@ public class MainWindow extends JFrame {
|
||||
public void menuCanceled(MenuEvent e) {
|
||||
}
|
||||
}
|
||||
|
||||
private class JPackagePopUp extends JPopupMenu {
|
||||
JMenuItem excludeItem = new JCheckBoxMenuItem("Exclude");
|
||||
|
||||
public JPackagePopUp(JPackage pkg) {
|
||||
excludeItem.setSelected(!pkg.isEnabled());
|
||||
add(excludeItem);
|
||||
excludeItem.addItemListener(e -> {
|
||||
String fullName = pkg.getFullName();
|
||||
if (excludeItem.isSelected()) {
|
||||
wrapper.addExcludedPackage(fullName);
|
||||
}
|
||||
else {
|
||||
wrapper.removeExcludedPackage(fullName);
|
||||
}
|
||||
reOpenFile();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -191,4 +193,13 @@ public class Utils {
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static void setWindowIcons(Window window) {
|
||||
List<Image> icons = new ArrayList<>();
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-16px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-32px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo-48px.png"));
|
||||
icons.add(Utils.openImage("/logos/jadx-logo.png"));
|
||||
window.setIconImages(icons);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 814 B |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 252.23 252"><defs><style>.cls-1,.cls-14{fill:none;}.cls-2{fill:#231f20;}.cls-3{fill:#461bbc;}.cls-4{clip-path:url(#clip-path);}.cls-5{clip-path:url(#clip-path-2);}.cls-6{fill:#008275;}.cls-7{font-size:132.44px;}.cls-12,.cls-7{fill:#fff;font-family:FuturaPT-Heavy, Futura PT;}.cls-8{letter-spacing:-0.03em;}.cls-9{clip-path:url(#clip-path-3);}.cls-10{fill:#f15a29;}.cls-11{fill:#9e1f63;}.cls-12{font-size:134.04px;letter-spacing:-0.01em;}.cls-13{letter-spacing:0em;}.cls-14{stroke:#fff;stroke-miterlimit:10;stroke-width:8.91px;}</style><clipPath id="clip-path" transform="translate(0.23)"><circle class="cls-1" cx="126" cy="126" r="125.72"/></clipPath><clipPath id="clip-path-2" transform="translate(0.23)"><polygon class="cls-1" points="45.29 223.57 176.25 75.9 258.09 69.98 258.09 242.47 45.29 242.47 45.29 223.57"/></clipPath><clipPath id="clip-path-3" transform="translate(0.23)"><polygon class="cls-1" points="57.93 195.53 179.7 63.23 69.69 5.58 3.53 19.58 -15.33 132.35 57.93 195.53"/></clipPath></defs><title>jadxlogo</title><circle class="cls-2" cx="126.23" cy="126" r="126"/><path class="cls-3" d="M122.66-68.19" transform="translate(0.23)"/><g class="cls-4"><g class="cls-5"><polygon class="cls-3" points="198.71 223.57 37.48 223.57 142.14 105.88 198.71 223.57"/><polygon class="cls-6" points="170.52 74.83 238.84 87.48 247.15 198.65 190.4 207.09 142.14 105.88 170.52 74.83"/><text class="cls-7" transform="translate(82.68 208.04) scale(0.95 1)"><tspan class="cls-8">D</tspan><tspan x="86.48" y="0">X</tspan></text></g><g class="cls-9"><polyline class="cls-10" points="73.68 193.85 0 126.28 140.52 53.49 171.12 88.7"/><polyline class="cls-11" points="30.86 19.58 30.86 110.86 129.83 59.59 160.42 94.8 181.86 67.19"/><text class="cls-12" transform="translate(10.71 121.84) scale(1.01 1)">J<tspan class="cls-13" x="54.29" y="0">A</tspan></text></g></g><circle class="cls-14" cx="126.08" cy="125.86" r="120.86"/></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
set -e
|
||||
|
||||
export JFROG_CLI_OFFER_CONFIG=false
|
||||
export JFROG_CLI_LOG_LEVEL=DEBUG
|
||||
|
||||
npm install -g jfrog-cli-go
|
||||
|
||||
TARGET=skylot/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION}
|
||||
CREDENTIALS="--user=skylot --key=${BINTRAY_KEY}"
|
||||
TARGET=${BINTRAY_USER}/jadx/${BINTRAY_PACKAGE}/v${JADX_VERSION}
|
||||
CREDENTIALS="--user=${BINTRAY_USER} --key=${BINTRAY_KEY}"
|
||||
|
||||
jfrog bt version-create ${TARGET} ${CREDENTIALS} --desc=${JADX_VERSION}
|
||||
jfrog bt upload 'build/jadx.*\.(zip|exe)' ${TARGET} ${CREDENTIALS} --regexp=true --publish=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
set -e
|
||||
|
||||
# upload coverage to codecov
|
||||
./gradlew clean build jacocoTestReport
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -xe
|
||||
set -e
|
||||
|
||||
npm install -g semantic-release
|
||||
npm install -g semantic-release/exec
|
||||
|
||||
Reference in New Issue
Block a user