Commit 5cee498e authored by Skylot's avatar Skylot

Merge branch 'master' into type-inference-wip

parents b689efcc db1b027d
......@@ -7,6 +7,7 @@ import com.android.dx.rop.code.AccessFlags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
......@@ -85,6 +86,9 @@ public class MethodGen {
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (Consts.DEBUG) {
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
}
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
......
......@@ -3,6 +3,8 @@ package jadx.core.dex.visitors;
import java.util.List;
import java.util.Objects;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
......@@ -31,7 +33,10 @@ import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "ClassModifier",
desc = "Remove synthetic classes, methods and fields",
runAfter = ModVisitor.class
runAfter = {
ModVisitor.class,
FixAccessModifiers.class
}
)
public class ClassModifier extends AbstractVisitor {
......@@ -218,6 +223,10 @@ public class ClassModifier extends AbstractVisitor {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
if (wrappedMth != null) {
AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags();
if (wrappedAccFlags.isStatic()) {
return false;
}
if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) {
return false;
}
......@@ -235,8 +244,9 @@ public class ClassModifier extends AbstractVisitor {
if (Objects.equals(wrappedMth.getAlias(), alias)) {
return true;
}
if (!wrappedMth.isVirtual()) {
return false;
if (!wrappedAccFlags.isPublic()) {
// must be public
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC);
}
wrappedMth.getMethodInfo().setAlias(alias);
return true;
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
......@@ -25,22 +26,27 @@ public class FixAccessModifiers extends AbstractVisitor {
if (respectAccessModifiers) {
return;
}
AccessInfo accessFlags = mth.getAccessFlags();
int newVisFlag = fixVisibility(mth, accessFlags);
int newVisFlag = fixVisibility(mth);
if (newVisFlag != 0) {
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
if (newAccFlags != accessFlags) {
mth.setAccFlags(newAccFlags);
mth.addComment("Access modifiers changed, original: " + accessFlags.rawString());
}
changeVisibility(mth, newVisFlag);
}
}
public static void changeVisibility(MethodNode mth, int newVisFlag) {
AccessInfo accessFlags = mth.getAccessFlags();
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
if (newAccFlags != accessFlags) {
mth.setAccFlags(newAccFlags);
mth.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString());
}
}
private int fixVisibility(MethodNode mth, AccessInfo accessFlags) {
private static int fixVisibility(MethodNode mth) {
if (mth.isVirtual()) {
// make virtual methods public
return AccessFlags.ACC_PUBLIC;
} else {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isAbstract()) {
// make abstract methods public
return AccessFlags.ACC_PUBLIC;
......
......@@ -3,6 +3,7 @@ package jadx.core.dex.visitors.blocksmaker;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
......@@ -26,7 +27,6 @@ import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxOverflowException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.connect;
......@@ -207,7 +207,12 @@ public class BlockProcessor extends AbstractVisitor {
markLoops(mth);
// clear self dominance
basicBlocks.forEach(block -> block.getDoms().clear(block.getId()));
basicBlocks.forEach(block -> {
block.getDoms().clear(block.getId());
if (block.getDoms().isEmpty()) {
block.setDoms(EMPTY);
}
});
calcImmediateDominators(basicBlocks, entryBlock);
}
......@@ -271,11 +276,20 @@ public class BlockProcessor extends AbstractVisitor {
for (BlockNode exit : mth.getExitBlocks()) {
exit.setDomFrontier(EMPTY);
}
for (BlockNode block : mth.getBasicBlocks()) {
List<BlockNode> domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size());
Deque<BlockNode> stack = new LinkedList<>();
stack.push(mth.getEnterBlock());
while (!stack.isEmpty()) {
BlockNode node = stack.pop();
for (BlockNode dominated : node.getDominatesOn()) {
stack.push(dominated);
}
domSortedBlocks.add(node);
}
Collections.reverse(domSortedBlocks);
for (BlockNode block : domSortedBlocks) {
try {
computeBlockDF(mth, block);
} catch (StackOverflowError e) {
throw new JadxOverflowException("Failed compute block dominance frontier");
} catch (Exception e) {
throw new JadxRuntimeException("Failed compute block dominance frontier", e);
}
......@@ -286,7 +300,6 @@ public class BlockProcessor extends AbstractVisitor {
if (block.getDomFrontier() != null) {
return;
}
block.getDominatesOn().forEach(domBlock -> computeBlockDF(mth, domBlock));
List<BlockNode> blocks = mth.getBasicBlocks();
BitSet domFrontier = null;
for (BlockNode s : block.getSuccessors()) {
......@@ -299,6 +312,9 @@ public class BlockProcessor extends AbstractVisitor {
}
for (BlockNode c : block.getDominatesOn()) {
BitSet frontier = c.getDomFrontier();
if (frontier == null) {
throw new JadxRuntimeException("Dominance frontier not calculated for dominated block: " + c + ", from: " + block);
}
for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) {
if (blocks.get(p).getIDom() != block) {
if (domFrontier == null) {
......@@ -308,7 +324,7 @@ public class BlockProcessor extends AbstractVisitor {
}
}
}
if (domFrontier == null || domFrontier.cardinality() == 0) {
if (domFrontier == null || domFrontier.isEmpty()) {
domFrontier = EMPTY;
}
block.setDomFrontier(domFrontier);
......
......@@ -266,7 +266,18 @@ public class IfMakerHelper {
result.setIfBlock(first.getIfBlock());
result.merge(first, second);
BlockNode otherPathBlock = followThenBranch ? first.getElseBlock() : first.getThenBlock();
BlockNode otherPathBlock;
if (followThenBranch) {
otherPathBlock = first.getElseBlock();
if (!otherPathBlock.equals(result.getElseBlock())) {
result.getSkipBlocks().add(otherPathBlock);
}
} else {
otherPathBlock = first.getThenBlock();
if (!otherPathBlock.equals(result.getThenBlock())) {
result.getSkipBlocks().add(otherPathBlock);
}
}
skipSimplePath(otherPathBlock, result.getSkipBlocks());
return result;
}
......
......@@ -80,35 +80,34 @@ public class LiveVarAnalysis {
private void processLiveInfo() {
int bbCount = mth.getBasicBlocks().size();
int regsCount = mth.getRegsCount();
BitSet[] liveIn = initBitSetArray(bbCount, regsCount);
BitSet[] liveInBlocks = initBitSetArray(bbCount, regsCount);
List<BlockNode> blocks = mth.getBasicBlocks();
int blocksSize = blocks.size();
int blocksCount = blocks.size();
int iterationsLimit = blocksCount * 10;
boolean changed;
int k = 0;
do {
changed = false;
for (int i = 0; i < blocksSize; i++) {
BlockNode block = blocks.get(i);
for (BlockNode block : blocks) {
int blockId = block.getId();
BitSet prevIn = liveIn[blockId];
BitSet prevIn = liveInBlocks[blockId];
BitSet newIn = new BitSet(regsCount);
List<BlockNode> successors = block.getSuccessors();
for (int s = 0, successorsSize = successors.size(); s < successorsSize; s++) {
newIn.or(liveIn[successors.get(s).getId()]);
for (BlockNode successor : block.getSuccessors()) {
newIn.or(liveInBlocks[successor.getId()]);
}
newIn.andNot(defs[blockId]);
newIn.or(uses[blockId]);
if (!prevIn.equals(newIn)) {
changed = true;
liveIn[blockId] = newIn;
liveInBlocks[blockId] = newIn;
}
}
if (k++ > 1000) {
throw new JadxRuntimeException("Live variable analysis reach iterations limit");
if (k++ > iterationsLimit) {
throw new JadxRuntimeException("Live variable analysis reach iterations limit, blocks count: " + blocksCount);
}
} while (changed);
this.liveIn = liveIn;
this.liveIn = liveInBlocks;
}
private static BitSet[] initBitSetArray(int length, int bitsCount) {
......
package jadx.core.dex.visitors.ssa;
import java.util.Arrays;
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.MethodNode;
final class RenameState {
private final MethodNode mth;
private final BlockNode block;
private final SSAVar[] vars;
private final int[] versions;
public static RenameState init(MethodNode mth) {
int regsCount = mth.getRegsCount();
RenameState state = new RenameState(
mth,
mth.getEnterBlock(),
new SSAVar[regsCount],
new int[regsCount]
);
for (RegisterArg arg : mth.getArguments(true)) {
state.startVar(arg);
}
return state;
}
public static RenameState copyFrom(RenameState state, BlockNode block) {
return new RenameState(
state.mth,
block,
Arrays.copyOf(state.vars, state.vars.length),
state.versions
);
}
private RenameState(MethodNode mth, BlockNode block, SSAVar[] vars, int[] versions) {
this.mth = mth;
this.block = block;
this.vars = vars;
this.versions = versions;
}
public BlockNode getBlock() {
return block;
}
public SSAVar getVar(int regNum) {
return vars[regNum];
}
public void startVar(RegisterArg regArg) {
int regNum = regArg.getRegNum();
int version = versions[regNum]++;
vars[regNum] = mth.makeNewSVar(regNum, version, regArg);
}
}
package jadx.core.dex.visitors.ssa;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Deque;
import java.util.Iterator;
......@@ -123,35 +122,31 @@ public class SSATransform extends AbstractVisitor {
}
private static void renameVariables(MethodNode mth) {
int regsCount = mth.getRegsCount();
SSAVar[] vars = new SSAVar[regsCount];
int[] versions = new int[regsCount];
// init method arguments
for (RegisterArg arg : mth.getArguments(true)) {
int regNum = arg.getRegNum();
vars[regNum] = newSSAVar(mth, versions, arg, regNum);
}
BlockNode enterBlock = mth.getEnterBlock();
initPhiInEnterBlock(vars, enterBlock);
renameVar(mth, vars, versions, enterBlock);
}
RenameState initState = RenameState.init(mth);
initPhiInEnterBlock(initState);
private static SSAVar newSSAVar(MethodNode mth, int[] versions, RegisterArg arg, int regNum) {
int version = versions[regNum]++;
return mth.makeNewSVar(regNum, version, arg);
Deque<RenameState> stack = new LinkedList<>();
stack.push(initState);
while (!stack.isEmpty()) {
RenameState state = stack.pop();
renameVarsInBlock(state);
for (BlockNode dominated : state.getBlock().getDominatesOn()) {
stack.push(RenameState.copyFrom(state, dominated));
}
}
}
private static void initPhiInEnterBlock(SSAVar[] vars, BlockNode enterBlock) {
PhiListAttr phiList = enterBlock.get(AType.PHI_LIST);
private static void initPhiInEnterBlock(RenameState initState) {
PhiListAttr phiList = initState.getBlock().get(AType.PHI_LIST);
if (phiList != null) {
for (PhiInsn phiInsn : phiList.getList()) {
bindPhiArg(vars, enterBlock, phiInsn);
bindPhiArg(initState, phiInsn);
}
}
}
private static void renameVar(MethodNode mth, SSAVar[] vars, int[] vers, BlockNode block) {
SSAVar[] inputVars = Arrays.copyOf(vars, vars.length);
private static void renameVarsInBlock(RenameState state) {
BlockNode block = state.getBlock();
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() != InsnType.PHI) {
for (InsnArg arg : insn.getArguments()) {
......@@ -160,18 +155,17 @@ public class SSATransform extends AbstractVisitor {
}
RegisterArg reg = (RegisterArg) arg;
int regNum = reg.getRegNum();
SSAVar var = vars[regNum];
SSAVar var = state.getVar(regNum);
if (var == null) {
throw new JadxRuntimeException("Not initialized variable reg: " + regNum
+ ", insn: " + insn + ", block:" + block + ", method: " + mth);
+ ", insn: " + insn + ", block:" + block);
}
var.use(reg);
}
}
RegisterArg result = insn.getResult();
if (result != null) {
int regNum = result.getRegNum();
vars[regNum] = newSSAVar(mth, vers, result, regNum);
state.startVar(result);
}
}
for (BlockNode s : block.getSuccessors()) {
......@@ -180,22 +174,18 @@ public class SSATransform extends AbstractVisitor {
continue;
}
for (PhiInsn phiInsn : phiList.getList()) {
bindPhiArg(vars, block, phiInsn);
bindPhiArg(state, phiInsn);
}
}
for (BlockNode domOn : block.getDominatesOn()) {
renameVar(mth, vars, vers, domOn);
}
System.arraycopy(inputVars, 0, vars, 0, vars.length);
}
private static void bindPhiArg(SSAVar[] vars, BlockNode block, PhiInsn phiInsn) {
private static void bindPhiArg(RenameState state, PhiInsn phiInsn) {
int regNum = phiInsn.getResult().getRegNum();
SSAVar var = vars[regNum];
SSAVar var = state.getVar(regNum);
if (var == null) {
return;
}
RegisterArg arg = phiInsn.bindArg(block);
RegisterArg arg = phiInsn.bindArg(state.getBlock());
var.use(arg);
var.setUsedInPhi(phiInsn);
}
......
package jadx.tests.integration.conditions;
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 TestComplexIf extends SmaliTest {
/*
public final class TestComplexIf {
private String a;
private int b;
private float c;
public final boolean test() {
if (this.a.equals("GT-P6200") || this.a.equals("GT-P6210") || ... ) {
return true;
}
if (this.a.equals("SM-T810") || this.a.equals("SM-T813") || ...) {
return false;
}
return this.c > 160.0f ? true : this.c <= 0.0f && ((this.b & 15) == 4 ? 1 : null) != null;
}
}
*/
@Test
public void test() {
ClassNode cls = getClassNodeFromSmaliWithPkg("conditions", "TestComplexIf");
String code = cls.getCode().toString();
assertThat(code, containsOne("if (this.a.equals(\"GT-P6200\") || this.a.equals(\"GT-P6210\") || this.a.equals(\"A100\") " +
"|| this.a.equals(\"A101\") || this.a.equals(\"LIFETAB_S786X\") || this.a.equals(\"VS890 4G\")) {"));
}
}
package jadx.tests.integration.inner;
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.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
public class TestInner2Samples extends IntegrationTest {
public static class TestInner2 {
private String a;
public class A {
public A() {
a = "a";
}
public String a() {
return a;
}
}
private static String b;
public static class B {
public B() {
b = "b";
}
public String b() {
return b;
}
}
private String c;
private void setC(String c) {
this.c = c;
}
public class C {
public String c() {
setC("c");
return c;
}
}
private static String d;
private static void setD(String s) {
d = s;
}
public static class D {
public String d() {
setD("d");
return d;
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestInner2.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("setD(\"d\");"));
assertThat(code, not(containsString("synthetic")));
assertThat(code, not(containsString("access$")));
}
}
package jadx.tests.integration.inner;
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.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
/**
* Issue: https://github.com/skylot/jadx/issues/397
*/
public class TestSyntheticMthRename extends SmaliTest {
// public class TestCls {
// public interface I<R, P> {
// R call(P... p);
// }
//
// public static final class A implements I<String, Runnable> {
// public /* synthetic */ /* virtual */ Object call(Object[] objArr) {
// return renamedCall((Runnable[]) objArr);
// }
//
// private /* varargs */ /* direct */ String renamedCall(Runnable... p) {
// return "str";
// }
// }
// }
@Test
public void test() {
ClassNode cls = getClassNodeFromSmaliFiles("inner", "TestSyntheticMthRename", "TestCls",
"TestCls", "TestCls$I", "TestCls$A"
);
String code = cls.getCode().toString();
assertThat(code, containsOne("public String call(Runnable... p) {"));
assertThat(code, not(containsString("synthetic")));
}
}
This diff is collapsed.
.class public final Linner/TestCls$A;
.super Ljava/lang/Object;
.source "TestCls.java"
# interfaces
.implements Linner/TestCls$I;
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Linner/TestCls;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x19
name = "A"
.end annotation
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/lang/Object;",
"Linner/TestCls$I",
"<",
"Ljava/lang/String;",
"Ljava/lang/Runnable;",
">;"
}
.end annotation
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 9
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method private varargs renamedCall([Ljava/lang/Runnable;)Ljava/lang/String;
.registers 3
.param p1, "p" # [Ljava/lang/Runnable;
.prologue
.line 12
const-string v0, "str"
return-object v0
.end method
# virtual methods
.method public synthetic call([Ljava/lang/Object;)Ljava/lang/Object;
.registers 3
.prologue
.line 9
check-cast p1, [Ljava/lang/Runnable;
invoke-virtual {p0, p1}, Linner/TestCls$A;->renamedCall([Ljava/lang/Runnable;)Ljava/lang/String;
move-result-object v0
return-object v0
.end method
.class public interface abstract Linner/TestCls$I;
.super Ljava/lang/Object;
.source "TestCls.java"
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Linner/TestCls;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x609
name = "I"
.end annotation
.annotation system Ldalvik/annotation/Signature;
value = {
"<R:",
"Ljava/lang/Object;",
"P:",
"Ljava/lang/Object;",
">",
"Ljava/lang/Object;"
}
.end annotation
# virtual methods
.method public varargs abstract call([Ljava/lang/Object;)Ljava/lang/Object;
.annotation system Ldalvik/annotation/Signature;
value = {
"([TP;)TR;"
}
.end annotation
.end method
.class public Linner/TestCls;
.super Ljava/lang/Object;
.source "TestCls.java"
# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Linner/TestCls$A;,
Linner/TestCls$I;
}
.end annotation
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment