Skip to content

Commit

Permalink
fix: support multi-exception catch blocks (#421)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jan 19, 2019
1 parent b0e3cfe commit aec9864
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 65 deletions.
23 changes: 16 additions & 7 deletions jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package jadx.core.codegen;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

Expand All @@ -11,6 +12,7 @@
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
Expand Down Expand Up @@ -306,16 +308,23 @@ private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws Co
return;
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
Expand Down
9 changes: 8 additions & 1 deletion jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import java.io.File;

import org.jetbrains.annotations.NotNull;

import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;

public final class ClassInfo {
public final class ClassInfo implements Comparable<ClassInfo> {

private final ArgType type;
private String pkg;
Expand Down Expand Up @@ -194,4 +196,9 @@ public boolean equals(Object obj) {
}
return false;
}

@Override
public int compareTo(@NotNull ClassInfo o) {
return fullName.compareTo(o.fullName);
}
}
51 changes: 31 additions & 20 deletions jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,16 @@ public void load() throws DecodeException {

DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
this.regsCount = mthCode.getRegistersSize();
initMethodTypes();

InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
instructions = decoder.process();
codeSize = instructions.length;
this.instructions = decoder.process();
this.codeSize = instructions.length;

initTryCatches(mthCode);
initJumps();
initTryCatches(this, mthCode, instructions);
initJumps(instructions);

this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
Expand Down Expand Up @@ -257,45 +257,45 @@ public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
}

private void initTryCatches(Code mthCode) {
InsnNode[] insnByOffset = instructions;
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
if (catchBlocks.length == 0 && tries.length == 0) {
return;
}

int hc = 0;
int handlersCount = 0;
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);

for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < handler.getAddresses().length; i++) {
int addr = handler.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
int[] handlerAddrArr = handler.getAddresses();
for (int i = 0; i < handlerAddrArr.length; i++) {
int addr = handlerAddrArr[i];
ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(mth, addr, type);
addrs.add(addr);
hc++;
handlersCount++;
}
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
tcBlock.addHandler(mth, addr, null);
addrs.add(addr);
hc++;
handlersCount++;
}
}

if (hc > 0 && hc != addrs.size()) {
if (handlersCount > 0 && handlersCount != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
for (TryCatchBlock ct1 : catches) {
for (TryCatchBlock ct2 : catches) {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(this, h);
ct2.removeHandler(mth, h);
h.setTryBlock(ct1);
}
}
Expand All @@ -309,6 +309,7 @@ private void initTryCatches(Code mthCode) {
for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset();
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
// TODO: don't override existing attribute
insnByOffset[addr].addAttr(ehAttr);
}
}
Expand All @@ -335,8 +336,7 @@ private void initTryCatches(Code mthCode) {
}
}

private void initJumps() {
InsnNode[] insnByOffset = instructions;
private static void initJumps(InsnNode[] insnByOffset) {
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
Expand Down Expand Up @@ -484,7 +484,18 @@ public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
exceptionHandlers = new ArrayList<>(2);
} else {
for (ExceptionHandler h : exceptionHandlers) {
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
if (h.equals(handler)) {
return h;
}
if (h.getHandleOffset() == handler.getHandleOffset()) {
if (h.getTryBlock() == handler.getTryBlock()) {
for (ClassInfo catchType : handler.getCatchTypes()) {
h.addCatchType(catchType);
}
} else {
// same handlers from different try blocks
// will merge later
}
return h;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public ExceptionHandler getHandler() {
public String toString() {
return "ExcHandler: " + (handler.isFinally()
? " FINALLY"
: (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg());
: handler.catchTypeStr() + " " + handler.getArg());
}
}
100 changes: 72 additions & 28 deletions jadx-core/src/main/java/jadx/core/dex/trycatch/ExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package jadx.core.dex.trycatch;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import org.jetbrains.annotations.Nullable;

import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;

public class ExceptionHandler {

private final ClassInfo catchType;
private final Set<ClassInfo> catchTypes = new TreeSet<>();
private final int handleOffset;

private BlockNode handlerBlock;
Expand All @@ -23,17 +32,57 @@ public class ExceptionHandler {
private TryCatchBlock tryBlock;
private boolean isFinally;

public ExceptionHandler(int addr, ClassInfo type) {
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
this.handleOffset = addr;
this.catchType = type;
addCatchType(type);
}

/**
* Add exception type to catch block
* @param type - null for 'all' or 'Throwable' handler
*/
public void addCatchType(@Nullable ClassInfo type) {
if (type != null) {
this.catchTypes.add(type);
} else {
if (!this.catchTypes.isEmpty()) {
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
}
}
}

public void addCatchTypes(Collection<ClassInfo> types) {
for (ClassInfo type : types) {
addCatchType(type);
}
}

public ClassInfo getCatchType() {
return catchType;
public Set<ClassInfo> getCatchTypes() {
return catchTypes;
}

public ArgType getArgType() {
if (isCatchAll()) {
return ArgType.THROWABLE;
}
Set<ClassInfo> types = getCatchTypes();
if (types.size() == 1) {
return types.iterator().next().getType();
} else {
return ArgType.THROWABLE;
}
}

public boolean isCatchAll() {
return catchType == null || catchType.getFullName().equals(Consts.CLASS_THROWABLE);
if (catchTypes.isEmpty()) {
return true;
}
for (ClassInfo classInfo : catchTypes) {
if (classInfo.getFullName().equals(Consts.CLASS_THROWABLE)) {
return true;
}
}
return false;
}

public int getHandleOffset() {
Expand Down Expand Up @@ -89,35 +138,30 @@ public void setFinally(boolean isFinally) {
}

@Override
public int hashCode() {
return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExceptionHandler other = (ExceptionHandler) obj;
if (catchType == null) {
if (other.catchType != null) {
return false;
}
} else if (!catchType.equals(other.catchType)) {
if (o == null || getClass() != o.getClass()) {
return false;
}
return handleOffset == other.handleOffset;
ExceptionHandler that = (ExceptionHandler) o;
return handleOffset == that.handleOffset &&
catchTypes.equals(that.catchTypes) &&
Objects.equals(tryBlock, that.tryBlock);
}

@Override
public int hashCode() {
return Objects.hash(catchTypes, handleOffset /*, tryBlock*/);
}

public String catchTypeStr() {
return catchTypes.isEmpty() ? "all" : Utils.listToString(catchTypes, " | ", ClassInfo::getShortName);
}

@Override
public String toString() {
return (catchType == null ? "all"
: catchType.getShortName()) + " -> " + InsnUtils.formatOffset(handleOffset);
return catchTypeStr() + " -> " + InsnUtils.formatOffset(handleOffset);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.util.LinkedList;
import java.util.List;

import org.jetbrains.annotations.Nullable;

import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
Expand Down Expand Up @@ -40,12 +42,14 @@ public boolean containsAllHandlers(TryCatchBlock tb) {
return handlers.containsAll(tb.handlers);
}

public ExceptionHandler addHandler(MethodNode mth, int addr, ClassInfo type) {
public ExceptionHandler addHandler(MethodNode mth, int addr, @Nullable ClassInfo type) {
ExceptionHandler handler = new ExceptionHandler(addr, type);
handler = mth.addExceptionHandler(handler);
handlers.add(handler);
handler.setTryBlock(this);
return handler;
ExceptionHandler addedHandler = mth.addExceptionHandler(handler);
if (addedHandler == handler || addedHandler.getTryBlock() != this) {
handlers.add(addedHandler);
}
return addedHandler;
}

public void removeHandler(MethodNode mth, ExceptionHandler handler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ private static void processMoveException(BlockNode block, InsnNode insn, Instruc

// result arg used both in this insn and exception handler,
RegisterArg resArg = insn.getResult();
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
ArgType type = excHandler.getArgType();
String name = excHandler.isCatchAll() ? "th" : "e";
if (resArg.getName() == null) {
resArg.setName(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private static void markExceptionHandlers(BlockNode block) {
return;
}
ExceptionHandler excHandler = handlerAttr.getHandler();
ArgType argType = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
ArgType argType = excHandler.getArgType();
if (!block.getInstructions().isEmpty()) {
InsnNode me = block.getInstructions().get(0);
if (me.getType() == InsnType.MOVE_EXCEPTION) {
Expand Down
Loading

0 comments on commit aec9864

Please sign in to comment.