/*
* AstBuilder.java
*
* Copyright (c) 2013 Mike Strobel
*
* This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
* and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0.
* A copy of the license can be found in the License.html file at the root of this distribution.
* By using this source code in any fashion, you are agreeing to be bound by the terms of the
* Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*/
package com.strobel.decompiler.ast;
import com.strobel.annotations.NotNull;
import com.strobel.assembler.flowanalysis.ControlFlowGraph;
import com.strobel.assembler.flowanalysis.ControlFlowGraphBuilder;
import com.strobel.assembler.flowanalysis.ControlFlowNode;
import com.strobel.assembler.flowanalysis.ControlFlowNodeType;
import com.strobel.assembler.ir.*;
import com.strobel.assembler.metadata.*;
import com.strobel.core.ArrayUtilities;
import com.strobel.core.Predicate;
import com.strobel.core.StringUtilities;
import com.strobel.core.StrongBox;
import com.strobel.core.VerifyArgument;
import com.strobel.decompiler.DecompilerContext;
import com.strobel.decompiler.InstructionHelper;
import java.util.*;
import static com.strobel.core.CollectionUtilities.*;
import static java.lang.String.format;
public final class AstBuilder {
private final static AstCode[] CODES = AstCode.values();
private final static StackSlot[] EMPTY_STACK = new StackSlot[0];
private final static ByteCode[] EMPTY_DEFINITIONS = new ByteCode[0];
private final Map<ExceptionHandler, ByteCode> _loadExceptions = new LinkedHashMap<>();
private final Set<Instruction> _leaveFinallyInstructions = new LinkedHashSet<>();
private Map<Instruction, Instruction> _originalInstructionMap;
private InstructionCollection _instructions;
private List<ExceptionHandler> _exceptionHandlers;
private MethodBody _body;
private boolean _optimize;
private DecompilerContext _context;
public static List<Node> build(final MethodBody body, final boolean optimize, final DecompilerContext context) {
final AstBuilder builder = new AstBuilder();
builder._body = VerifyArgument.notNull(body, "body");
builder._optimize = optimize;
builder._context = VerifyArgument.notNull(context, "context");
if (body.getInstructions().isEmpty()) {
return Collections.emptyList();
}
builder.analyzeHandlers();
/*
final ControlFlowGraph cfg = ControlFlowGraphBuilder.build(builder._instructions, builder._exceptionHandlers);
cfg.computeDominance();
cfg.computeDominanceFrontier();
cfg.export(
new File(
format(
"w:/dump/%s_%s.gv",
body.getMethod().getDeclaringType().getFullName().replace('$', '.'),
body.getMethod().getName().replace('<', '_').replace('>', '_')
)
)
);
*/
final List<ByteCode> byteCode = builder.performStackAnalysis();
@SuppressWarnings("UnnecessaryLocalVariable")
final List<Node> ast = builder.convertToAst(byteCode, new LinkedHashSet<>(builder._exceptionHandlers));
return ast;
}
private static List<ExceptionHandler> remapHandlers(final List<ExceptionHandler> handlers, final InstructionCollection instructions) {
final List<ExceptionHandler> newHandlers = new ArrayList<>();
for (final ExceptionHandler handler : handlers) {
final ExceptionBlock oldTry = handler.getTryBlock();
final ExceptionBlock oldHandler = handler.getHandlerBlock();
final ExceptionBlock newTry = new ExceptionBlock(
instructions.atOffset(oldTry.getFirstInstruction().getOffset()),
instructions.atOffset(oldTry.getLastInstruction().getOffset())
);
final ExceptionBlock newHandler = new ExceptionBlock(
instructions.atOffset(oldHandler.getFirstInstruction().getOffset()),
instructions.atOffset(oldHandler.getLastInstruction().getOffset())
);
if (handler.isCatch()) {
newHandlers.add(
ExceptionHandler.createCatch(
newTry,
newHandler,
handler.getCatchType()
)
);
}
else {
newHandlers.add(
ExceptionHandler.createFinally(
newTry,
newHandler
)
);
}
}
return newHandlers;
}
private final static class HandlerInfo {
final ExceptionHandler handler;
final List<ControlFlowNode> allNodes = new ArrayList<>();
final List<ControlFlowNode> tryNodes = new ArrayList<>();
final List<ControlFlowNode> handlerNodes = new ArrayList<>();
HandlerInfo(final ExceptionHandler handler) {
this.handler = handler;
}
}
@SuppressWarnings("ConstantConditions")
private InstructionCollection analyzeHandlers() {
final Map<ExceptionHandler, HandlerInfo> handlers = new LinkedHashMap<>();
final InstructionCollection oldInstructions = _body.getInstructions();
final InstructionCollection instructions = _instructions = copyInstructions(oldInstructions);
final List<ExceptionHandler> exceptionHandlers = _exceptionHandlers = remapHandlers(_body.getExceptionHandlers(), instructions);
final Map<Instruction, Instruction> originalInstructionMap = _originalInstructionMap = new IdentityHashMap<>();
for (int i = 0, n = instructions.size(); i < n; i++) {
originalInstructionMap.put(instructions.get(i), oldInstructions.get(i));
}
//
// Remove finally handlers that indirectly handle themselves.
//
for (int i = 0; i < _exceptionHandlers.size(); i++) {
final ExceptionHandler handler = _exceptionHandlers.get(i);
if (handler.isFinally()) {
final ExceptionBlock handlerBlock = handler.getHandlerBlock();
for (int j = i + 1; j < _exceptionHandlers.size(); j++) {
final ExceptionHandler otherHandler = _exceptionHandlers.get(j);
final ExceptionBlock otherTryBlock = otherHandler.getTryBlock();
final ExceptionBlock otherHandlerBlock = otherHandler.getHandlerBlock();
if (otherHandler.isFinally() &&
otherHandlerBlock.getFirstInstruction() == handlerBlock.getFirstInstruction() &&
otherHandlerBlock.getLastInstruction() == handlerBlock.getLastInstruction() &&
otherTryBlock.getFirstInstruction().getOffset() >= handlerBlock.getFirstInstruction().getOffset() &&
otherTryBlock.getLastInstruction().getOffset() < handlerBlock.getLastInstruction().getEndOffset()) {
_exceptionHandlers.remove(j);
}
}
final ExceptionBlock tryBlock = handler.getTryBlock();
if (tryBlock.getLastInstruction().getOffset() >=
handlerBlock.getFirstInstruction().getOffset()) {
_exceptionHandlers.set(
i,
ExceptionHandler.createFinally(
new ExceptionBlock(
tryBlock.getFirstInstruction(),
handlerBlock.getFirstInstruction().getPrevious()
),
handlerBlock
)
);
}
}
}
closeTryHandlerGaps(instructions, exceptionHandlers);
final ControlFlowGraph cfg = ControlFlowGraphBuilder.build(instructions, exceptionHandlers);
cfg.computeDominance();
cfg.computeDominanceFrontier();
for (int i = 0; i < exceptionHandlers.size(); i++) {
final ExceptionHandler handler = exceptionHandlers.get(i);
final HandlerInfo handlerInfo = new HandlerInfo(handler);
for (final ControlFlowNode node : cfg.getNodes()) {
if (node.getNodeType() == ControlFlowNodeType.Normal) {
if (node.getStart().getOffset() >= handler.getTryBlock().getFirstInstruction().getOffset() &&
node.getEnd().getOffset() <= handler.getTryBlock().getLastInstruction().getOffset()) {
handlerInfo.tryNodes.add(node);
handlerInfo.allNodes.add(node);
}
else if (node.getStart().getOffset() >= handler.getHandlerBlock().getFirstInstruction().getOffset() &&
node.getEnd().getOffset() <= handler.getHandlerBlock().getLastInstruction().getOffset()) {
handlerInfo.handlerNodes.add(node);
handlerInfo.allNodes.add(node);
}
}
}
handlers.put(handler, handlerInfo);
}
includeLeaveTryInstructions(handlers, exceptionHandlers, cfg);
analyzeHandlers(instructions, handlers);
return instructions;
}
private void includeLeaveTryInstructions(
final Map<ExceptionHandler, HandlerInfo> handlers,
final List<ExceptionHandler> exceptionHandlers,
final ControlFlowGraph cfg) {
final List<HandlerInfo> handlersCopy = toList(handlers.values());
for (int i = 0; i < exceptionHandlers.size(); i++) {
final ExceptionHandler handler = exceptionHandlers.get(i);
final HandlerInfo handlerInfo = handlers.get(handler);
if (handlerInfo == null) {
continue;
}
//
// Look for trailing branch instructions between a try block and its handler and adjust the
// try block range to include them.
//
if (!handlerInfo.tryNodes.isEmpty() &&
!handlerInfo.handlerNodes.isEmpty()) {
final ControlFlowNode lastTryNode = handlerInfo.tryNodes.get(handlerInfo.tryNodes.size() - 1);
final HandlerInfo nearestHandler = findNearestHandler(handlerInfo, handlersCopy);
final ControlFlowNode lastCatchNode = nearestHandler.handlerNodes.get(nearestHandler.handlerNodes.size() - 1);
if (lastTryNode.getBlockIndex() < lastCatchNode.getBlockIndex() - 1) {
final ControlFlowNode nodeAfterTry = cfg.getNodes().get(lastTryNode.getBlockIndex() + 1);
if (nodeAfterTry != null &&
nodeAfterTry.getNodeType() == ControlFlowNodeType.Normal &&
nodeAfterTry.getStart() == nodeAfterTry.getEnd() &&
nodeAfterTry.getEnd().getNext() == nearestHandler.handlerNodes.get(0).getStart() &&
nodeAfterTry.getStart().getOpCode().isUnconditionalBranch()) {
handlerInfo.tryNodes.add(nodeAfterTry);
if (handler.isCatch()) {
exceptionHandlers.set(
i,
ExceptionHandler.createCatch(
new ExceptionBlock(
handler.getTryBlock().getFirstInstruction(),
nodeAfterTry.getStart()
),
handler.getHandlerBlock(),
handler.getCatchType()
)
);
}
else {
exceptionHandlers.set(
i,
ExceptionHandler.createFinally(
new ExceptionBlock(
handler.getTryBlock().getFirstInstruction(),
nodeAfterTry.getStart()
),
handler.getHandlerBlock()
)
);
}
handlers.remove(handler);
handlers.put(exceptionHandlers.get(i), handlerInfo);
}
}
}
}
}
private void closeTryHandlerGaps(final InstructionCollection instructions, final List<ExceptionHandler> exceptionHandlers) {
//
// Java does this retarded thing where a try block gets split along exit branches,
// but with the split parts sharing the same handler. We can't represent this in
// out AST, so just merge the parts back together.
//
for (int i = 0; i < exceptionHandlers.size() - 1; i++) {
final ExceptionHandler current = exceptionHandlers.get(i);
final ExceptionHandler next = exceptionHandlers.get(i + 1);
if (current.getHandlerBlock().getFirstInstruction() == next.getHandlerBlock().getFirstInstruction() &&
current.getHandlerBlock().getLastInstruction() == next.getHandlerBlock().getLastInstruction()) {
final Instruction lastInCurrent = current.getTryBlock().getLastInstruction();
final Instruction firstInNext = next.getTryBlock().getFirstInstruction();
final Instruction branchInBetween = firstInNext.getPrevious();
final Instruction beforeBranch;
if (branchInBetween != null) {
beforeBranch = instructions.tryGetAtOffset(
branchInBetween.getOffset() - branchInBetween.getOpCode().getSize() + branchInBetween.getOpCode().getStackChange()
);
}
else {
beforeBranch = null;
}
if (branchInBetween != null &&
branchInBetween.getOpCode().isBranch() &&
lastInCurrent == beforeBranch) {
final ExceptionHandler newHandler;
if (current.isFinally()) {
newHandler = ExceptionHandler.createFinally(
new ExceptionBlock(
current.getTryBlock().getFirstInstruction(),
next.getTryBlock().getLastInstruction()
),
new ExceptionBlock(
current.getHandlerBlock().getFirstInstruction(),
current.getHandlerBlock().getLastInstruction()
)
);
}
else {
newHandler = ExceptionHandler.createCatch(
new ExceptionBlock(
current.getTryBlock().getFirstInstruction(),
next.getTryBlock().getLastInstruction()
),
new ExceptionBlock(
current.getHandlerBlock().getFirstInstruction(),
current.getHandlerBlock().getLastInstruction()
),
current.getCatchType()
);
}
exceptionHandlers.set(i, newHandler);
exceptionHandlers.remove(i + 1);
}
}
}
}
private HandlerInfo findNearestHandler(final HandlerInfo handler, final Collection<HandlerInfo> handlers) {
final Instruction tryStart = handler.handler.getTryBlock().getFirstInstruction();
final Instruction tryEnd = handler.handler.getTryBlock().getLastInstruction();
HandlerInfo nearestHandler = handler;
int nearestHandlerStart = handler.handler.getHandlerBlock().getFirstInstruction().getOffset();
for (final HandlerInfo h : handlers) {
final ExceptionBlock tryBlock = h.handler.getTryBlock();
final ExceptionBlock handlerBlock = h.handler.getHandlerBlock();
if (tryBlock.getFirstInstruction() == tryStart &&
tryBlock.getLastInstruction() == tryEnd &&
handlerBlock.getFirstInstruction().getOffset() < nearestHandlerStart &&
!h.handlerNodes.isEmpty()) {
nearestHandler = h;
nearestHandlerStart = handlerBlock.getFirstInstruction().getOffset();
}
}
return nearestHandler;
}
@SuppressWarnings("ConstantConditions")
private void analyzeHandlers(final InstructionCollection instructions, final Map<ExceptionHandler, HandlerInfo> handlers) {
final Set<Instruction> toRemove = new LinkedHashSet<>();
final Map<Instruction, Instruction> remappedJumps = new LinkedHashMap<>();
for (final ExceptionHandler handler : _exceptionHandlers) {
if (!handler.isFinally()) {
continue;
}
final Set<Instruction> rethrowingFinallyStarts = new LinkedHashSet<>();
final ExceptionBlock handlerTry = handler.getTryBlock();
for (final ExceptionHandler other : _exceptionHandlers) {
final ExceptionBlock otherTry = other.getTryBlock();
final HandlerInfo finallyInfo = handlers.get(handler);
final ControlFlowNode firstInFinally = firstOrDefault(finallyInfo.handlerNodes);
if (firstInFinally == null) {
continue;
}
Instruction firstFinallyInstruction = firstInFinally.getStart();
Instruction lastFinallyInstruction = lastOrDefault(finallyInfo.handlerNodes).getEnd();
if (lastFinallyInstruction.getOpCode() == OpCode.ATHROW &&
lastFinallyInstruction.getPrevious() != null) {
//
// If a finally handler catches, stores, and rethrows an exception, NOP-out the
// instructions responsible for those operations. In the decompiled output, we
// don't expect to see such behavior.
//
final Instruction rethrowException = lastFinallyInstruction;
final Instruction loadException;
final Instruction storeException;
switch (firstFinallyInstruction.getOpCode()) {
case ASTORE:
case ASTORE_1:
case ASTORE_2:
case ASTORE_3:
case ASTORE_W:
storeException = firstFinallyInstruction;
firstFinallyInstruction = firstFinallyInstruction.getNext();
break;
default:
storeException = null;
break;
}
final Instruction previousFinallyInstruction = lastFinallyInstruction.getPrevious();
switch (previousFinallyInstruction.getOpCode()) {
case ALOAD:
case ALOAD_1:
case ALOAD_2:
case ALOAD_3:
case ALOAD_W:
loadException = previousFinallyInstruction;
lastFinallyInstruction = previousFinallyInstruction.getPrevious();
break;
default:
loadException = null;
break;
}
if (storeException != null && loadException != null) {
rethrowingFinallyStarts.add(storeException);
storeException.setOpCode(OpCode.POP);
storeException.setOperand(null);
loadException.setOpCode(OpCode.NOP);
loadException.setOperand(null);
rethrowException.setOpCode(OpCode.NOP);
if (rethrowException.getNext() == null) {
_leaveFinallyInstructions.add(rethrowException);
}
}
}
else if (rethrowingFinallyStarts.contains(firstFinallyInstruction)) {
firstFinallyInstruction = firstFinallyInstruction.getNext();
lastFinallyInstruction = lastFinallyInstruction.getPrevious().getPrevious();
}
if (otherTry.getFirstInstruction().getOffset() == handlerTry.getFirstInstruction().getOffset() &&
otherTry.getLastInstruction().getOffset() == handlerTry.getLastInstruction().getOffset()) {
final HandlerInfo handlerInfo = handlers.get(other);
final ControlFlowNode lastInTry = lastOrDefault(handlerInfo.tryNodes);
if (lastInTry == null || lastInTry.precedes(firstInFinally)) {
continue;
}
ControlFlowNode firstAfterTry = null;
final Instruction lastInstructionInTry = lastInTry.getEnd();
final Instruction firstInstructionAfterTry = lastInstructionInTry.getNext();
if (firstInstructionAfterTry == null) {
continue;
}
for (final ControlFlowNode successor : lastInTry.getSuccessors()) {
if (successor.getStart() == firstInstructionAfterTry) {
firstAfterTry = successor;
break;
}
}
if (firstAfterTry == null || firstAfterTry == firstInFinally) {
continue;
}
assert firstAfterTry != null;
Instruction pTry, pFinally;
pTry = firstAfterTry.getStart();
pFinally = firstFinallyInstruction;
if ((pTry.getOpCode() == OpCode.GOTO ||
pTry.getOpCode() == OpCode.GOTO_W) &&
((Instruction) pTry.getOperand(0)).getOffset() > lastFinallyInstruction.getOffset()) {
final Instruction oldBranchTarget = pTry.getOperand(0);
//
// If the try block ends with an explicit break bypassing the finally handler, redirect it.
//
remappedJumps.put(oldBranchTarget, firstFinallyInstruction);
pTry.setOperand(firstFinallyInstruction);
pTry = pTry.getPrevious();
//
// If we were at an instruction that jumped past the finally, then our position must be
// after the inlined handler code and not before. Jump back the appropriate number of
// instructions.
//
pTry = _instructions.tryGetAtOffset(
pTry.getOffset() - (lastFinallyInstruction.getOffset() - firstFinallyInstruction.getOffset())
);
}
else if (pTry.getOpCode().getFlowControl() == FlowControl.Return) {
//
// If we were at a return instruction, then our position must be after the inlined handler
// code and not before. Jump back the appropriate number of instructions.
//
pTry = pTry.getPrevious();
pTry = _instructions.tryGetAtOffset(
pTry.getOffset() - (lastFinallyInstruction.getOffset() - firstFinallyInstruction.getOffset())
);
}
// boolean first = true;
while (pFinally != null &&
pTry != null &&
pFinally.getOffset() <= lastFinallyInstruction.getOffset() &&
pTry.getOpCode() == pFinally.getOpCode()) {
if (pTry.getLabel() != null) {
remappedJumps.put(pTry, pFinally);
}
// if (first) {
// if (pTry.getLabel() != null) {
// remappedJumps.put(pTry, firstFinallyInstruction);
// }
//
// first = false;
// }
toRemove.add(pTry);
pTry = pTry.getNext();
pFinally = pFinally.getNext();
}
}
}
}
if (toRemove.isEmpty()) {
return;
}
for (final Instruction instruction : toRemove) {
updateHandlersForRemovedInstruction(toRemove, instruction);
instructions.remove(instruction);
}
instructions.recomputeOffsets();
for (final Instruction instruction : instructions) {
if (instruction.hasLabel()) {
instruction.getLabel().setIndex(instruction.getOffset());
}
if (instruction.getOperandCount() == 0) {
continue;
}
final Object operand = instruction.getOperand(0);
if (operand instanceof Instruction) {
final Instruction oldTarget = (Instruction) operand;
final Instruction newTarget = remappedJumps.get(oldTarget);
if (newTarget != null) {
instruction.setOperand(newTarget);
if (!newTarget.hasLabel()) {
newTarget.setLabel(new com.strobel.assembler.metadata.Label(newTarget.getOffset()));
}
}
}
else if (operand instanceof SwitchInfo) {
final SwitchInfo oldOperand = (SwitchInfo) operand;
final Instruction oldDefault = oldOperand.getDefaultTarget();
final Instruction newDefault = remappedJumps.get(oldDefault);
if (newDefault != null && !newDefault.hasLabel()) {
newDefault.setLabel(new com.strobel.assembler.metadata.Label(newDefault.getOffset()));
}
final Instruction[] oldTargets = oldOperand.getTargets();
Instruction[] newTargets = null;
for (int i = 0; i < oldTargets.length; i++) {
final Instruction newTarget = remappedJumps.get(oldTargets[i]);
if (newTarget != null) {
if (newTargets == null) {
newTargets = Arrays.copyOf(oldTargets, oldTargets.length);
}
newTargets[i] = newTarget;
if (!newTarget.hasLabel()) {
newTarget.setLabel(new com.strobel.assembler.metadata.Label(newTarget.getOffset()));
}
}
}
if (newDefault != null || newTargets != null) {
final SwitchInfo newOperand = new SwitchInfo(
oldOperand.getKeys(),
newDefault != null ? newDefault : oldDefault,
newTargets != null ? newTargets : oldTargets
);
instruction.setOperand(newOperand);
}
}
}
final ControlFlowGraph cfg = ControlFlowGraphBuilder.build(instructions, _exceptionHandlers);
cfg.computeDominance();
cfg.computeDominanceFrontier();
//
// Remove handlers that directly handle themselves.
//
for (int i = 0; i < _exceptionHandlers.size(); i++) {
final ExceptionHandler handler = _exceptionHandlers.get(i);
if (handler.getTryBlock().contains(handler.getHandlerBlock())) {
_exceptionHandlers.remove(i--);
}
}
includeLeaveTryInstructions(handlers, _exceptionHandlers, cfg);
}
private void updateHandlersForRemovedInstruction(final Set<Instruction> toRemove, final Instruction instruction) {
for (int i = 0; i < _exceptionHandlers.size(); i++) {
ExceptionHandler handler = _exceptionHandlers.get(i);
Instruction first = handler.getHandlerBlock().getFirstInstruction();
Instruction last = handler.getHandlerBlock().getLastInstruction();
boolean changed = false;
if (first == instruction) {
while (first != null &&
toRemove.contains(first) &&
first.getOffset() < last.getEndOffset()) {
first = first.getNext();
changed = true;
}
}
if (first != null && last == instruction) {
while (last != null &&
toRemove.contains(last) &&
last.getOffset() > first.getOffset()) {
last = last.getPrevious();
changed = true;
}
}
if (changed) {
if (first == null ||
last == null ||
toRemove.contains(first) ||
toRemove.contains(last)) {
_exceptionHandlers.remove(i--);
}
else if (handler.isCatch()) {
handler = ExceptionHandler.createCatch(
handler.getTryBlock(),
new ExceptionBlock(first, last),
handler.getCatchType()
);
_exceptionHandlers.set(i, handler);
}
else {
handler = ExceptionHandler.createFinally(
handler.getTryBlock(),
new ExceptionBlock(first, last)
);
_exceptionHandlers.set(i, handler);
}
}
}
}
private static InstructionCollection copyInstructions(final InstructionCollection instructions) {
final InstructionCollection instructionsCopy = new InstructionCollection();
for (final Instruction instruction : instructions) {
final Instruction copy = new Instruction(instruction.getOffset(), instruction.getOpCode());
if (instruction.getOperandCount() > 1) {
final Object[] operands = new Object[instruction.getOperandCount()];
for (int i = 0; i < operands.length; i++) {
operands[i] = instruction.getOperand(i);
}
copy.setOperand(operands);
}
else {
copy.setOperand(instruction.getOperand(0));
}
copy.setLabel(instruction.getLabel());
instructionsCopy.add(copy);
}
for (final Instruction instruction : instructionsCopy) {
if (!instruction.hasOperand()) {
continue;
}
final Object operand = instruction.getOperand(0);
if (operand instanceof Instruction) {
instruction.setOperand(instructionsCopy.atOffset(((Instruction) operand).getOffset()));
}
else if (operand instanceof SwitchInfo) {
final SwitchInfo oldOperand = (SwitchInfo) operand;
final Instruction oldDefault = oldOperand.getDefaultTarget();
final Instruction newDefault = instructionsCopy.atOffset(oldDefault.getOffset());
final Instruction[] oldTargets = oldOperand.getTargets();
final Instruction[] newTargets = new Instruction[oldTargets.length];
for (int i = 0; i < newTargets.length; i++) {
newTargets[i] = instructionsCopy.atOffset(oldTargets[i].getOffset());
}
final SwitchInfo newOperand = new SwitchInfo(oldOperand.getKeys(), newDefault, newTargets);
instruction.setOperand(newOperand);
}
}
return instructionsCopy;
}
@SuppressWarnings("ConstantConditions")
private List<ByteCode> performStackAnalysis() {
final Map<Instruction, ByteCode> byteCodeMap = new LinkedHashMap<>();
final InstructionCollection instructions = _instructions;
final List<ExceptionHandler> exceptionHandlers = _exceptionHandlers;
final List<ByteCode> body = new ArrayList<>(instructions.size());
final StackMappingVisitor stackMapper = new StackMappingVisitor();
final InstructionVisitor instructionVisitor = stackMapper.visitBody(_body);
final StrongBox<AstCode> codeBox = new StrongBox<>();
final StrongBox<Object> operandBox = new StrongBox<>();
final CoreMetadataFactory factory = CoreMetadataFactory.make(_context.getCurrentType(), _context.getCurrentMethod());
for (final Instruction instruction : instructions) {
final OpCode opCode = instruction.getOpCode();
AstCode code = _leaveFinallyInstructions.contains(instruction) ? AstCode.Leave : CODES[opCode.ordinal()];
Object operand = instruction.hasOperand() ? instruction.getOperand(0) : null;
final Object secondOperand = instruction.getOperandCount() > 1 ? instruction.getOperand(1) : null;
codeBox.set(code);
operandBox.set(operand);
final int offset = _originalInstructionMap.get(instruction).getOffset();
if (AstCode.expandMacro(codeBox, operandBox, _body, offset)) {
code = codeBox.get();
operand = operandBox.get();
}
final ByteCode byteCode = new ByteCode();
byteCode.instruction = instruction;
byteCode.offset = instruction.getOffset();
byteCode.endOffset = instruction.getEndOffset();
byteCode.code = code;
byteCode.operand = operand;
byteCode.secondOperand = secondOperand;
byteCode.popCount = InstructionHelper.getPopDelta(instruction, _body);
byteCode.pushCount = InstructionHelper.getPushDelta(instruction, _body);
byteCodeMap.put(instruction, byteCode);
body.add(byteCode);
}
for (int i = 0, n = body.size() - 1; i < n; i++) {
final ByteCode next = body.get(i + 1);
final ByteCode current = body.get(i);
current.next = next;
next.previous = current;
}
final Stack<ByteCode> agenda = new Stack<>();
final VariableDefinitionCollection variables = _body.getVariables();
final int variableCount = variables.slotCount();
final Set<ByteCode> exceptionHandlerStarts = new LinkedHashSet<>(exceptionHandlers.size());
final VariableSlot[] unknownVariables = VariableSlot.makeUnknownState(variableCount);
final MethodReference method = _body.getMethod();
final List<ParameterDefinition> parameters = method.getParameters();
final boolean hasThis = _body.hasThis();
if (hasThis) {
if (method.isConstructor()) {
unknownVariables[0] = new VariableSlot(FrameValue.UNINITIALIZED_THIS, EMPTY_DEFINITIONS);
}
else {
unknownVariables[0] = new VariableSlot(FrameValue.makeReference(_context.getCurrentType()), EMPTY_DEFINITIONS);
}
}
for (int i = 0; i < parameters.size(); i++) {
final ParameterDefinition parameter = parameters.get(i);
final TypeReference parameterType = parameter.getParameterType();
final int slot = parameter.getSlot();
switch (parameterType.getSimpleType()) {
case Boolean:
case Byte:
case Character:
case Short:
case Integer:
unknownVariables[slot] = new VariableSlot(FrameValue.INTEGER, EMPTY_DEFINITIONS);
break;
case Long:
unknownVariables[slot] = new VariableSlot(FrameValue.LONG, EMPTY_DEFINITIONS);
unknownVariables[slot + 1] = new VariableSlot(FrameValue.TOP, EMPTY_DEFINITIONS);
break;
case Float:
unknownVariables[slot] = new VariableSlot(FrameValue.FLOAT, EMPTY_DEFINITIONS);
break;
case Double:
unknownVariables[slot] = new VariableSlot(FrameValue.DOUBLE, EMPTY_DEFINITIONS);
unknownVariables[slot + 1] = new VariableSlot(FrameValue.TOP, EMPTY_DEFINITIONS);
break;
default:
unknownVariables[slot] = new VariableSlot(FrameValue.makeReference(parameterType), EMPTY_DEFINITIONS);
break;
}
}
for (final ExceptionHandler handler : exceptionHandlers) {
final ByteCode handlerStart = byteCodeMap.get(handler.getHandlerBlock().getFirstInstruction());
handlerStart.stackBefore = EMPTY_STACK;
handlerStart.variablesBefore = unknownVariables;
final ByteCode loadException = new ByteCode();
final TypeReference catchType;
if (handler.isFinally()) {
catchType = factory.makeNamedType("java.lang.Throwable");
}
else {
catchType = handler.getCatchType();
}
loadException.code = AstCode.LoadException;
loadException.operand = catchType;
loadException.popCount = 0;
loadException.pushCount = 1;
_loadExceptions.put(handler, loadException);
handlerStart.stackBefore = new StackSlot[] {
new StackSlot(
FrameValue.makeReference(catchType),
new ByteCode[] { loadException }
)
};
exceptionHandlerStarts.add(handlerStart);
agenda.push(handlerStart);
}
body.get(0).stackBefore = EMPTY_STACK;
body.get(0).variablesBefore = unknownVariables;
agenda.push(body.get(0));
//
// Process agenda.
//
while (!agenda.isEmpty()) {
final ByteCode byteCode = agenda.pop();
//
// Calculate new stack.
//
stackMapper.visitFrame(byteCode.getFrameBefore());
instructionVisitor.visit(byteCode.instruction);
final StackSlot[] newStack = createModifiedStack(byteCode, stackMapper);
//
// Calculate new variable state.
//
final VariableSlot[] newVariableState = VariableSlot.cloneVariableState(byteCode.variablesBefore);
final Map<Instruction, TypeReference> initializations = stackMapper.getInitializations();
for (int i = 0; i < newVariableState.length; i++) {
final VariableSlot slot = newVariableState[i];
if (slot.isUninitialized()) {
final Object parameter = slot.value.getParameter();
if (parameter instanceof Instruction) {
final Instruction instruction = (Instruction) parameter;
final TypeReference initializedType = initializations.get(instruction);
if (initializedType != null) {
newVariableState[i] = new VariableSlot(
FrameValue.makeReference(initializedType),
slot.definitions
);
}
}
}
}
if (byteCode.isVariableDefinition()) {
final int slot = ((VariableReference) byteCode.operand).getSlot();
newVariableState[slot] = new VariableSlot(
stackMapper.getLocalValue(slot),
new ByteCode[] { byteCode }
);
}
/*
if (byteCode.code == AstCode.Goto ||
byteCode.code == AstCode.__GotoW) {
//
// After GOTO, finally block might have touched the variables.
//
newVariableState = unknownVariables;
}
*/
//
// Find all successors.
//
final ArrayList<ByteCode> branchTargets = new ArrayList<>();
if (!byteCode.code.isUnconditionalControlFlow()) {
if (exceptionHandlerStarts.contains(byteCode.next)) {
//
// Do not fall through down to exception handler. It's invalid bytecode,
// but a non-compliant compiler could produce it.
//
}
else {
branchTargets.add(byteCode.next);
}
}
if (byteCode.operand instanceof Instruction[]) {
for (final Instruction instruction : (Instruction[]) byteCode.operand) {
final ByteCode target = byteCodeMap.get(instruction);
branchTargets.add(target);
//
// Branch target must have a label.
//
if (target.label == null) {
target.label = new Label();
target.label.setName(target.makeLabelName());
}
}
}
else if (byteCode.operand instanceof Instruction) {
final Instruction instruction = (Instruction) byteCode.operand;
final ByteCode target = byteCodeMap.get(instruction);
branchTargets.add(target);
//
// Branch target must have a label.
//
if (target.label == null) {
target.label = new Label();
target.label.setName(target.makeLabelName());
}
}
else if (byteCode.operand instanceof SwitchInfo) {
final SwitchInfo switchInfo = (SwitchInfo) byteCode.operand;
final ByteCode defaultTarget = byteCodeMap.get(switchInfo.getDefaultTarget());
branchTargets.add(defaultTarget);
//
// Branch target must have a label.
//
if (defaultTarget.label == null) {
defaultTarget.label = new Label();
defaultTarget.label.setName(defaultTarget.makeLabelName());
}
for (final Instruction instruction : switchInfo.getTargets()) {
final ByteCode target = byteCodeMap.get(instruction);
branchTargets.add(target);
//
// Branch target must have a label.
//
if (target.label == null) {
target.label = new Label();
target.label.setName(target.makeLabelName());
}
}
}
else if (byteCode.code == AstCode.Ret || byteCode.code == AstCode.__RetW) {
final VariableDefinition temp = (VariableDefinition) byteCode.operand;
final FrameValue address = stackMapper.getLocalValue(temp.getSlot());
final Instruction targetInstruction = (Instruction) address.getParameter();
final ByteCode target = byteCodeMap.get(targetInstruction);
branchTargets.add(target);
}
//
// Apply the state to successors.
//
for (final ByteCode branchTarget : branchTargets) {
if (branchTarget.stackBefore == null && branchTarget.variablesBefore == null) {
if (branchTargets.size() == 1) {
branchTarget.stackBefore = newStack;
branchTarget.variablesBefore = newVariableState;
}
else {
//
// Do not share data for several bytecodes.
//
branchTarget.stackBefore = StackSlot.modifyStack(newStack, 0, null);
branchTarget.variablesBefore = VariableSlot.cloneVariableState(newVariableState);
}
agenda.push(branchTarget);
}
else {
if (branchTarget.stackBefore.length != newStack.length) {
throw new IllegalStateException("Inconsistent stack size at " + byteCode.name() + ".");
}
//
// Be careful not to change our new data; it might be reused for several branch targets.
// In general, be careful that two bytecodes never share data structures.
//
boolean modified = false;
//
// Merge stacks; modify the target.
//
for (int i = 0; i < newStack.length; i++) {
final ByteCode[] oldDefinitions = branchTarget.stackBefore[i].definitions;
final ByteCode[] newDefinitions = ArrayUtilities.union(oldDefinitions, newStack[i].definitions);
if (newDefinitions.length > oldDefinitions.length) {
branchTarget.stackBefore[i] = new StackSlot(newStack[i].value, newDefinitions);
modified = true;
}
}
//
// Merge variables; modify the target;
//
for (int i = 0; i < newVariableState.length; i++) {
final VariableSlot oldSlot = branchTarget.variablesBefore[i];
final VariableSlot newSlot = newVariableState[i];
if (!oldSlot.isUninitialized()) {
if (newSlot.isUninitialized()) {
branchTarget.variablesBefore[i] = newSlot;
modified = true;
}
else {
final ByteCode[] oldDefinitions = oldSlot.definitions;
final ByteCode[] newDefinitions = ArrayUtilities.union(oldSlot.definitions, newSlot.definitions);
if (newDefinitions.length > oldDefinitions.length) {
branchTarget.variablesBefore[i] = new VariableSlot(oldSlot.value, newDefinitions);
modified = true;
}
}
}
}
if (modified) {
agenda.push(branchTarget);
}
}
}
}
//
// Occasionally, compilers or obfuscators may generate unreachable code (which might be intentionally invalid).
// It should be safe to simply remove it.
//
ArrayList<ByteCode> unreachable = null;
for (final ByteCode byteCode : body) {
if (byteCode.stackBefore == null) {
if (unreachable == null) {
unreachable = new ArrayList<>();
}
unreachable.add(byteCode);
}
}
if (unreachable != null) {
body.removeAll(unreachable);
}
//
// Generate temporary variables to replace stack values.
//
for (final ByteCode byteCode : body) {
final int popCount = byteCode.popCount != -1 ? byteCode.popCount : byteCode.stackBefore.length;
int argumentIndex = 0;
for (int i = byteCode.stackBefore.length - popCount; i < byteCode.stackBefore.length; i++) {
final Variable tempVariable = new Variable();
tempVariable.setName(format("stack_%1$02X_%2$d", byteCode.offset, argumentIndex));
tempVariable.setGenerated(true);
final FrameValue value = byteCode.stackBefore[i].value;
switch (value.getType()) {
case Integer:
tempVariable.setType(BuiltinTypes.Integer);
break;
case Float:
tempVariable.setType(BuiltinTypes.Float);
break;
case Long:
tempVariable.setType(BuiltinTypes.Long);
break;
case Double:
tempVariable.setType(BuiltinTypes.Double);
break;
case UninitializedThis:
tempVariable.setType(_context.getCurrentType());
break;
case Reference:
TypeReference refType = (TypeReference) value.getParameter();
if (refType.isWildcardType()) {
refType = refType.hasSuperBound() ? refType.getSuperBound() : refType.getExtendsBound();
}
tempVariable.setType(refType);
break;
}
byteCode.stackBefore[i] = new StackSlot(value, byteCode.stackBefore[i].definitions, tempVariable);
for (final ByteCode pushedBy : byteCode.stackBefore[i].definitions) {
if (pushedBy.storeTo == null) {
pushedBy.storeTo = new ArrayList<>();
}
pushedBy.storeTo.add(tempVariable);
}
argumentIndex++;
}
}
//
// Try to use a single temporary variable instead of several, if possible (especially useful for DUP).
// This has to be done after all temporary variables are assigned so we know about all loads.
//
for (final ByteCode byteCode : body) {
if (byteCode.storeTo != null && byteCode.storeTo.size() > 1) {
final List<Variable> localVariables = byteCode.storeTo;
//
// For each of the variables, find the location where it is loaded; there should be exactly one.
//
List<StackSlot> loadedBy = null;
for (final Variable local : localVariables) {
inner:
for (final ByteCode bc : body) {
for (final StackSlot s : bc.stackBefore) {
if (s.loadFrom == local) {
if (loadedBy == null) {
loadedBy = new ArrayList<>();
}
loadedBy.add(s);
break inner;
}
}
}
}
if (loadedBy == null) {
continue;
}
//
// We know that all the temp variables have a single load; now make sure they have a single store.
//
boolean singleStore = true;
TypeReference type = null;
for (final StackSlot slot : loadedBy) {
if (slot.definitions.length != 1) {
singleStore = false;
break;
}
else if (slot.definitions[0] != byteCode) {
singleStore = false;
break;
}
else if (type == null) {
switch (slot.value.getType()) {
case Integer:
type = BuiltinTypes.Integer;
break;
case Float:
type = BuiltinTypes.Float;
break;
case Long:
type = BuiltinTypes.Long;
break;
case Double:
type = BuiltinTypes.Double;
break;
case Reference:
type = (TypeReference) slot.value.getParameter();
if (type.isWildcardType()) {
type = type.hasSuperBound() ? type.getSuperBound() : type.getExtendsBound();
}
break;
}
}
}
if (!singleStore) {
continue;
}
//
// We can now reduce everything into a single variable.
//
final Variable tempVariable = new Variable();
tempVariable.setName(format("expr_%1$02X", byteCode.offset));
tempVariable.setGenerated(true);
tempVariable.setType(type);
byteCode.storeTo = Collections.singletonList(tempVariable);
for (final ByteCode bc : body) {
for (int i = 0; i < bc.stackBefore.length; i++) {
//
// Is it one of the variables we merged?
//
if (localVariables.contains(bc.stackBefore[i].loadFrom)) {
//
// Replace with the new temp variable.
//
bc.stackBefore[i] = new StackSlot(bc.stackBefore[i].value, bc.stackBefore[i].definitions, tempVariable);
}
}
}
}
}
//
// Split and convert the normal local variables.
//
convertLocalVariables(body);
//
// Convert branch targets to labels.
//
for (final ByteCode byteCode : body) {
if (byteCode.operand instanceof Instruction[]) {
final Instruction[] branchTargets = (Instruction[]) byteCode.operand;
final Label[] newOperand = new Label[branchTargets.length];
for (int i = 0; i < branchTargets.length; i++) {
newOperand[i] = byteCodeMap.get(branchTargets[i]).label;
}
byteCode.operand = newOperand;
}
else if (byteCode.operand instanceof Instruction) {
//noinspection SuspiciousMethodCalls
byteCode.operand = byteCodeMap.get(byteCode.operand).label;
}
else if (byteCode.operand instanceof SwitchInfo) {
final SwitchInfo switchInfo = (SwitchInfo) byteCode.operand;
final Instruction[] branchTargets = ArrayUtilities.prepend(switchInfo.getTargets(), switchInfo.getDefaultTarget());
final Label[] newOperand = new Label[branchTargets.length];
for (int i = 0; i < branchTargets.length; i++) {
newOperand[i] = byteCodeMap.get(branchTargets[i]).label;
}
byteCode.operand = newOperand;
}
}
//
// Convert parameters to Variables.
//
// convertParameters(body);
return body; //removeRedundantFinallyBlocks(body, byteCodeMap, exceptionHandlers);
}
private static StackSlot[] createModifiedStack(final ByteCode byteCode, final StackMappingVisitor stackMapper) {
final Map<Instruction, TypeReference> initializations = stackMapper.getInitializations();
final StackSlot[] oldStack = byteCode.stackBefore.clone();
for (int i = 0; i < oldStack.length; i++) {
if (oldStack[i].value.getParameter() instanceof Instruction) {
final TypeReference initializedType = initializations.get(oldStack[i].value.getParameter());
if (initializedType != null) {
oldStack[i] = new StackSlot(
FrameValue.makeReference(initializedType),
oldStack[i].definitions,
oldStack[i].loadFrom
);
}
}
}
if (byteCode.popCount == 0 && byteCode.pushCount == 0) {
return oldStack;
}
switch (byteCode.code) {
case Dup:
return ArrayUtilities.append(
oldStack,
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case DupX1:
return ArrayUtilities.insert(
oldStack,
oldStack.length - 2,
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case DupX2:
return ArrayUtilities.insert(
oldStack,
oldStack.length - 3,
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case Dup2:
return ArrayUtilities.append(
oldStack,
new StackSlot(stackMapper.getStackValue(1), oldStack[oldStack.length - 2].definitions),
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case Dup2X1:
return ArrayUtilities.insert(
oldStack,
oldStack.length - 3,
new StackSlot(stackMapper.getStackValue(1), oldStack[oldStack.length - 2].definitions),
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case Dup2X2:
return ArrayUtilities.insert(
oldStack,
oldStack.length - 4,
new StackSlot(stackMapper.getStackValue(1), oldStack[oldStack.length - 2].definitions),
new StackSlot(stackMapper.getStackValue(0), oldStack[oldStack.length - 1].definitions)
);
case Swap:
final StackSlot[] newStack = new StackSlot[oldStack.length];
ArrayUtilities.copy(oldStack, newStack);
final StackSlot temp = newStack[oldStack.length - 1];
newStack[oldStack.length - 1] = newStack[oldStack.length - 2];
newStack[oldStack.length - 2] = temp;
return newStack;
default:
final FrameValue[] pushValues = new FrameValue[byteCode.pushCount];
for (int i = 0; i < byteCode.pushCount; i++) {
pushValues[pushValues.length - i - 1] = stackMapper.getStackValue(i);
}
return StackSlot.modifyStack(
oldStack,
byteCode.popCount != -1 ? byteCode.popCount : oldStack.length,
byteCode,
pushValues
);
}
}
private final static class VariableInfo {
final Variable variable;
final List<ByteCode> definitions;
final List<ByteCode> references;
VariableInfo(final Variable variable, final List<ByteCode> definitions, final List<ByteCode> references) {
this.variable = variable;
this.definitions = definitions;
this.references = references;
}
}
@SuppressWarnings("ConstantConditions")
private void convertLocalVariables(final List<ByteCode> body) {
final MethodDefinition method = _context.getCurrentMethod();
final List<ParameterDefinition> parameters = method.getParameters();
final VariableDefinitionCollection variables = _body.getVariables();
final ParameterDefinition[] parameterMap = new ParameterDefinition[variables.slotCount()];
final boolean hasThis = _body.hasThis();
if (hasThis) {
parameterMap[0] = _body.getThisParameter();
}
for (final ParameterDefinition parameter : parameters) {
parameterMap[parameter.getSlot()] = parameter;
}
for (final VariableDefinition variableDefinition : variables) {
//
// Find all definitions of and references to this variable.
//
final List<ByteCode> definitions = new ArrayList<>();
final List<ByteCode> references = new ArrayList<>();
for (final ByteCode b : body) {
if (b.operand == variableDefinition) {
if (b.isVariableDefinition()) {
definitions.add(b);
}
else {
references.add(b);
}
}
}
final List<VariableInfo> newVariables;
boolean fromUnknownDefinition = false;
final int variableIndex = variableDefinition.getSlot();
if (_optimize) {
for (final ByteCode b : references) {
if (b.variablesBefore[variableIndex].isUninitialized()) {
fromUnknownDefinition = true;
break;
}
}
}
final ParameterDefinition parameter = parameterMap[variableIndex];
if (parameter != null && variableDefinition.getScopeStart() == 0) {
final Variable variable = new Variable();
variable.setName(
StringUtilities.isNullOrEmpty(parameter.getName()) ? "p" + parameter.getPosition()
: parameter.getName()
);
variable.setType(parameter.getParameterType());
variable.setOriginalParameter(parameter);
final VariableInfo variableInfo = new VariableInfo(variable, definitions, references);
newVariables = Collections.singletonList(variableInfo);
}
else if (!_optimize || fromUnknownDefinition) {
final Variable variable = new Variable();
variable.setName(
StringUtilities.isNullOrEmpty(variableDefinition.getName()) ? "var_" + variableIndex
: variableDefinition.getName()
);
if (variableDefinition.isFromMetadata()) {
variable.setType(variableDefinition.getVariableType());
}
else {
for (final ByteCode b : definitions) {
final FrameValue stackValue = b.stackBefore[b.stackBefore.length - b.popCount].value;
if (stackValue != FrameValue.NULL &&
stackValue != FrameValue.UNINITIALIZED &&
stackValue != FrameValue.UNINITIALIZED_THIS) {
final TypeReference variableType;
switch (stackValue.getType()) {
case Integer:
variableType = BuiltinTypes.Integer;
break;
case Float:
variableType = BuiltinTypes.Float;
break;
case Long:
variableType = BuiltinTypes.Long;
break;
case Double:
variableType = BuiltinTypes.Double;
break;
case Uninitialized:
if (stackValue.getParameter() instanceof Instruction &&
((Instruction) stackValue.getParameter()).getOpCode() == OpCode.NEW) {
variableType = ((Instruction) stackValue.getParameter()).getOperand(0);
}
else {
variableType = variableDefinition.getVariableType();
}
break;
case UninitializedThis:
variableType = _context.getCurrentType();
break;
case Reference:
variableType = (TypeReference) stackValue.getParameter();
break;
default:
variableType = variableDefinition.getVariableType();
break;
}
variable.setType(variableType);
break;
}
}
if (variable.getType() == null) {
variable.setType(BuiltinTypes.Object);
}
}
variable.setOriginalVariable(variableDefinition);
variable.setGenerated(false);
final VariableInfo variableInfo = new VariableInfo(variable, definitions, references);
newVariables = Collections.singletonList(variableInfo);
}
else {
newVariables = new ArrayList<>();
for (final ByteCode b : definitions) {
final Variable variable = new Variable();
variable.setName(
format(
"%1$s_%2$02X",
StringUtilities.isNullOrEmpty(variableDefinition.getName()) ? "var_" + variableIndex
: variableDefinition.getName(),
b.offset
)
);
final TypeReference variableType;
final FrameValue stackValue;
if (b.code == AstCode.Inc) {
stackValue = FrameValue.INTEGER;
}
else {
stackValue = b.stackBefore[b.stackBefore.length - b.popCount].value;
}
if (variableDefinition.isFromMetadata()) {
variable.setType(variableDefinition.getVariableType());
}
else {
switch (stackValue.getType()) {
case Integer:
variableType = BuiltinTypes.Integer;
break;
case Float:
variableType = BuiltinTypes.Float;
break;
case Long:
variableType = BuiltinTypes.Long;
break;
case Double:
variableType = BuiltinTypes.Double;
break;
case UninitializedThis:
variableType = _context.getCurrentType();
break;
case Reference:
variableType = (TypeReference) stackValue.getParameter();
break;
default:
variableType = variableDefinition.getVariableType();
break;
}
variable.setType(variableType);
}
variable.setOriginalVariable(variableDefinition);
variable.setGenerated(false);
final VariableInfo variableInfo = new VariableInfo(
variable,
new ArrayList<ByteCode>(),
new ArrayList<ByteCode>()
);
variableInfo.definitions.add(b);
newVariables.add(variableInfo);
}
//
// Add loads to the data structure; merge variables if necessary.
//
for (final ByteCode ref : references) {
final ByteCode[] refDefinitions = ref.variablesBefore[variableIndex].definitions;
if (refDefinitions.length == 1) {
VariableInfo newVariable = null;
for (final VariableInfo v : newVariables) {
if (v.definitions.contains(refDefinitions[0])) {
newVariable = v;
break;
}
}
assert newVariable != null;
newVariable.references.add(ref);
}
else {
final ArrayList<VariableInfo> mergeVariables = new ArrayList<>();
for (final VariableInfo v : newVariables) {
boolean hasIntersection = false;
outer:
for (final ByteCode b1 : v.definitions) {
for (final ByteCode b2 : refDefinitions) {
if (b1 == b2) {
hasIntersection = true;
break outer;
}
}
}
if (hasIntersection) {
mergeVariables.add(v);
}
}
final ArrayList<ByteCode> mergedDefinitions = new ArrayList<>();
final ArrayList<ByteCode> mergedReferences = new ArrayList<>();
for (final VariableInfo v : mergeVariables) {
mergedDefinitions.addAll(v.definitions);
mergedReferences.addAll(v.references);
}
final VariableInfo mergedVariable = new VariableInfo(
mergeVariables.get(0).variable,
mergedDefinitions,
mergedReferences
);
mergedVariable.references.add(ref);
newVariables.removeAll(mergeVariables);
newVariables.add(mergedVariable);
}
}
}
//
// Set bytecode operands.
//
for (final VariableInfo newVariable : newVariables) {
for (final ByteCode definition : newVariable.definitions) {
definition.operand = newVariable.variable;
}
for (final ByteCode reference : newVariable.references) {
reference.operand = newVariable.variable;
}
}
}
}
@SuppressWarnings("ConstantConditions")
private List<Node> convertToAst(final List<ByteCode> body, final Set<ExceptionHandler> exceptionHandlers) {
final ArrayList<Node> ast = new ArrayList<>();
while (!exceptionHandlers.isEmpty()) {
final TryCatchBlock tryCatchBlock = new TryCatchBlock();
//
// Find the first and widest scope;
//
int tryStart = Integer.MAX_VALUE;
int tryEnd = -1;
int firstHandlerStart = -1;
for (final ExceptionHandler handler : exceptionHandlers) {
final int start = handler.getTryBlock().getFirstInstruction().getOffset();
if (start < tryStart) {
tryStart = start;
}
}
for (final ExceptionHandler handler : exceptionHandlers) {
final int start = handler.getTryBlock().getFirstInstruction().getOffset();
if (start == tryStart) {
final Instruction lastInstruction = handler.getTryBlock().getLastInstruction();
final int end = lastInstruction.getEndOffset();
if (end > tryEnd) {
tryEnd = end;
final int handlerStart = handler.getHandlerBlock().getFirstInstruction().getOffset();
if (firstHandlerStart < 0 || handlerStart < firstHandlerStart) {
firstHandlerStart = handlerStart;
}
}
}
}
final ArrayList<ExceptionHandler> handlers = new ArrayList<>();
for (final ExceptionHandler handler : exceptionHandlers) {
final int start = handler.getTryBlock().getFirstInstruction().getOffset();
final int end = handler.getTryBlock().getLastInstruction().getEndOffset();
if (start == tryStart && end == tryEnd) {
handlers.add(handler);
}
}
Collections.sort(
handlers,
new Comparator<ExceptionHandler>() {
@Override
public int compare(@NotNull final ExceptionHandler o1, @NotNull final ExceptionHandler o2) {
return Integer.compare(
o1.getTryBlock().getFirstInstruction().getOffset(),
o2.getTryBlock().getFirstInstruction().getOffset()
);
}
}
);
//
// Remember that any part of the body might have been removed due to unreachability.
//
//
// Cut all instructions up to the try block.
//
int tryStartIndex = 0;
while (tryStartIndex < body.size() &&
body.get(tryStartIndex).offset < tryStart) {
tryStartIndex++;
}
ast.addAll(convertToAst(body.subList(0, tryStartIndex)));
for (int i = 0; i < tryStartIndex; i++) {
body.remove(0);
}
//
// Cut the try block.
//
{
final Set<ExceptionHandler> nestedHandlers = new LinkedHashSet<>();
for (final ExceptionHandler eh : exceptionHandlers) {
final int ts = eh.getTryBlock().getFirstInstruction().getOffset();
final int te = eh.getTryBlock().getLastInstruction().getEndOffset();
if (tryStart <= ts && te < tryEnd ||
tryStart < ts && te <= tryEnd) {
nestedHandlers.add(eh);
}
}
exceptionHandlers.removeAll(nestedHandlers);
int tryEndIndex = 0;
while (tryEndIndex < body.size() && body.get(tryEndIndex).offset < tryEnd) {
tryEndIndex++;
}
final Block tryBlock = new Block();
final ArrayList<ByteCode> tryBody = new ArrayList<>(body.subList(0, tryEndIndex));
for (int i = 0; i < tryEndIndex; i++) {
body.remove(0);
}
final List<Node> tryAst = convertToAst(tryBody, nestedHandlers);
final Node lastInTry = lastOrDefault(tryAst);
if (lastInTry != null && !lastInTry.isUnconditionalControlFlow()) {
tryAst.add(new Expression(AstCode.Leave, null));
}
/*
if (ast.isEmpty() && !tryAst.isEmpty() && tryAst.get(0) instanceof Label) {
ast.add(tryAst.remove(0));
}
*/
tryBlock.getBody().addAll(tryAst);
tryCatchBlock.setTryBlock(tryBlock);
}
//
// Cut from the end of the try to the beginning of the first handler.
//
/*
while (!body.isEmpty() && body.get(0).offset < firstHandlerStart) {
body.remove(0);
}
*/
//
// Cut all handlers.
//
HandlerLoop:
for (final ExceptionHandler eh : handlers) {
final TypeReference catchType = eh.getCatchType();
final ExceptionBlock handlerBlock = eh.getHandlerBlock();
final int handlerStart = handlerBlock.getFirstInstruction().getOffset();
final int handlerEnd = handlerBlock.getLastInstruction() != null
? handlerBlock.getLastInstruction().getEndOffset()
: _body.getCodeSize();
int startIndex = 0;
while (startIndex < body.size() &&
body.get(startIndex).offset < handlerStart) {
startIndex++;
}
int endIndex = startIndex;
while (endIndex < body.size() &&
body.get(endIndex).offset < handlerEnd) {
endIndex++;
}
//
// See if we share a block with another handler; if so, add our catch type and move on.
//
for (final CatchBlock catchBlock : tryCatchBlock.getCatchBlocks()) {
final Expression firstExpression = firstOrDefault(
catchBlock.getSelfAndChildrenRecursive(Expression.class),
new Predicate<Expression>() {
@Override
public boolean test(final Expression e) {
return !e.getRanges().isEmpty();
}
}
);
if (firstExpression == null) {
continue;
}
final int otherHandlerStart = firstExpression.getRanges().get(0).getStart();
if (otherHandlerStart == handlerStart) {
catchBlock.getCaughtTypes().add(catchType);
catchBlock.setExceptionType(
MetadataHelper.findCommonSuperType(
catchBlock.getExceptionType(),
catchType
)
);
continue HandlerLoop;
}
}
final Set<ExceptionHandler> nestedHandlers = new LinkedHashSet<>();
for (final ExceptionHandler e : exceptionHandlers) {
final int ts = eh.getTryBlock().getFirstInstruction().getOffset();
final int te = eh.getTryBlock().getLastInstruction().getEndOffset();
final int hs = eh.getHandlerBlock().getFirstInstruction().getOffset();
final int he = eh.getHandlerBlock().getLastInstruction().getEndOffset();
if (hs == handlerStart && he == handlerEnd) {
continue;
}
if (handlerStart <= ts && te < handlerEnd ||
handlerStart < ts && te <= handlerEnd) {
nestedHandlers.add(e);
}
}
exceptionHandlers.removeAll(nestedHandlers);
final ArrayList<ByteCode> handlerCode = new ArrayList<>(body.subList(startIndex, endIndex));
for (int i = startIndex; i < endIndex; i++) {
body.remove(startIndex);
}
final List<Node> handlerAst = convertToAst(handlerCode, nestedHandlers);
final Node lastInHandler = lastOrDefault(handlerAst);
if (lastInHandler != null && !lastInHandler.isUnconditionalControlFlow()) {
handlerAst.add(new Expression(AstCode.Leave, null));
}
if (eh.isCatch()) {
final CatchBlock catchBlock = new CatchBlock();
catchBlock.setExceptionType(catchType);
catchBlock.getCaughtTypes().add(catchType);
catchBlock.getBody().addAll(handlerAst);
final ByteCode loadException = _loadExceptions.get(eh);
if (loadException.storeTo == null || loadException.storeTo.isEmpty()) {
//
// Exception is not used.
//
catchBlock.setExceptionVariable(null);
}
else if (loadException.storeTo.size() == 1) {
if (!catchBlock.getBody().isEmpty() &&
catchBlock.getBody().get(0) instanceof Expression &&
!((Expression) catchBlock.getBody().get(0)).getArguments().isEmpty()) {
final Expression first = (Expression) catchBlock.getBody().get(0);
final AstCode firstCode = first.getCode();
final Expression firstArgument = first.getArguments().get(0);
if (firstCode == AstCode.Pop &&
firstArgument.getCode() == AstCode.Load &&
firstArgument.getOperand() == loadException.storeTo.get(0)) {
//
// The exception is just popped; optimize it away.
//
if (_context.getSettings().getAlwaysGenerateExceptionVariableForCatchBlocks()) {
final Variable exceptionVariable = new Variable();
exceptionVariable.setName(format("ex_%1$02X", handlerStart));
exceptionVariable.setGenerated(true);
catchBlock.setExceptionVariable(exceptionVariable);
}
else {
catchBlock.setExceptionVariable(null);
}
}
else {
catchBlock.setExceptionVariable(loadException.storeTo.get(0));
}
}
}
else {
final Variable exceptionTemp = new Variable();
exceptionTemp.setName(format("ex_%1$02X", handlerStart));
exceptionTemp.setGenerated(true);
catchBlock.setExceptionVariable(exceptionTemp);
for (final Variable storeTo : loadException.storeTo) {
catchBlock.getBody().add(
0,
new Expression(AstCode.Store, storeTo, new Expression(AstCode.Load, exceptionTemp))
);
}
}
tryCatchBlock.getCatchBlocks().add(catchBlock);
}
else if (eh.isFinally()) {
final Block finallyBlock = new Block();
finallyBlock.getBody().addAll(handlerAst);
tryCatchBlock.setFinallyBlock(finallyBlock);
}
}
exceptionHandlers.removeAll(handlers);
final Expression first;
final Expression last;
first = firstOrDefault(tryCatchBlock.getTryBlock().getSelfAndChildrenRecursive(Expression.class));
if (!tryCatchBlock.getCatchBlocks().isEmpty()) {
last = lastOrDefault(lastOrDefault(tryCatchBlock.getCatchBlocks()).getSelfAndChildrenRecursive(Expression.class));
}
else {
last = lastOrDefault(tryCatchBlock.getFinallyBlock().getSelfAndChildrenRecursive(Expression.class));
}
if (first == null && last == null) {
//
// Ignore empty handlers. These can crop up due to finally blocks which handle themselves.
//
continue;
}
ast.add(tryCatchBlock);
}
ast.addAll(convertToAst(body));
return ast;
}
@SuppressWarnings("ConstantConditions")
private List<Node> convertToAst(final List<ByteCode> body) {
final ArrayList<Node> ast = new ArrayList<>();
//
// Convert stack-based bytecode to bytecode AST.
//
for (final ByteCode byteCode : body) {
final Instruction originalInstruction = _originalInstructionMap.get(byteCode.instruction);
final Range codeRange = new Range(originalInstruction.getOffset(), originalInstruction.getEndOffset());
if (byteCode.stackBefore == null) {
//
// Unreachable code.
//
continue;
}
//
// Include the instruction's label, if it has one.
//
if (byteCode.label != null) {
ast.add(byteCode.label);
}
switch (byteCode.code) {
case Dup:
case DupX1:
case DupX2:
case Dup2:
case Dup2X1:
case Dup2X2:
case Swap:
continue;
}
final Expression expression = new Expression(byteCode.code, byteCode.operand);
if (byteCode.code == AstCode.Inc) {
assert byteCode.secondOperand instanceof Integer;
expression.setCode(AstCode.Inc);
expression.getArguments().add(new Expression(AstCode.LdC, byteCode.secondOperand));
}
expression.getRanges().add(codeRange);
//
// Reference arguments using temporary variables.
//
final int popCount = byteCode.popCount != -1 ? byteCode.popCount
: byteCode.stackBefore.length;
for (int i = byteCode.stackBefore.length - popCount; i < byteCode.stackBefore.length; i++) {
final StackSlot slot = byteCode.stackBefore[i];
if (slot.value.getType().isDoubleWord()) {
i++;
}
expression.getArguments().add(new Expression(AstCode.Load, slot.loadFrom));
}
//
// Store the result to temporary variables, if needed.
//
if (byteCode.storeTo == null || byteCode.storeTo.isEmpty()) {
ast.add(expression);
}
else if (byteCode.storeTo.size() == 1) {
ast.add(new Expression(AstCode.Store, byteCode.storeTo.get(0), expression));
}
else {
final Variable tempVariable = new Variable();
tempVariable.setName(format("expr_%1$02X", byteCode.offset));
tempVariable.setGenerated(true);
ast.add(new Expression(AstCode.Store, tempVariable, expression));
for (int i = byteCode.storeTo.size() - 1; i >= 0; i--) {
ast.add(
new Expression(
AstCode.Store,
byteCode.storeTo.get(i),
new Expression(AstCode.Load, tempVariable)
)
);
}
}
}
return ast;
}
// <editor-fold defaultstate="collapsed" desc="StackSlot Class">
private final static class StackSlot {
final FrameValue value;
final ByteCode[] definitions;
final Variable loadFrom;
public StackSlot(final FrameValue value, final ByteCode[] definitions) {
this.value = VerifyArgument.notNull(value, "value");
this.definitions = VerifyArgument.notNull(definitions, "definitions");
this.loadFrom = null;
}
public StackSlot(final FrameValue value, final ByteCode[] definitions, final Variable loadFrom) {
this.value = VerifyArgument.notNull(value, "value");
this.definitions = VerifyArgument.notNull(definitions, "definitions");
this.loadFrom = loadFrom;
}
public static StackSlot[] modifyStack(
final StackSlot[] stack,
final int popCount,
final ByteCode pushDefinition,
final FrameValue... pushTypes) {
VerifyArgument.notNull(stack, "stack");
VerifyArgument.isNonNegative(popCount, "popCount");
VerifyArgument.noNullElements(pushTypes, "pushTypes");
final StackSlot[] newStack = new StackSlot[stack.length - popCount + pushTypes.length];
System.arraycopy(stack, 0, newStack, 0, stack.length - popCount);
for (int i = stack.length - popCount, j = 0; i < newStack.length; i++, j++) {
newStack[i] = new StackSlot(pushTypes[j], new ByteCode[] { pushDefinition });
}
return newStack;
}
@Override
public String toString() {
return "StackSlot(" + value + ')';
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="VariableSlot Class">
private final static class VariableSlot {
final static VariableSlot UNKNOWN_INSTANCE = new VariableSlot(FrameValue.UNINITIALIZED, EMPTY_DEFINITIONS);
final ByteCode[] definitions;
final FrameValue value;
public VariableSlot(final FrameValue value, final ByteCode[] definitions) {
this.value = VerifyArgument.notNull(value, "value");
this.definitions = VerifyArgument.notNull(definitions, "definitions");
}
public static VariableSlot[] cloneVariableState(final VariableSlot[] state) {
return VerifyArgument.notNull(state, "state").clone();
}
public static VariableSlot[] makeUnknownState(final int variableCount) {
final VariableSlot[] unknownVariableState = new VariableSlot[variableCount];
for (int i = 0; i < variableCount; i++) {
unknownVariableState[i] = UNKNOWN_INSTANCE;
}
return unknownVariableState;
}
public final boolean isUninitialized() {
return value == FrameValue.UNINITIALIZED || value == FrameValue.UNINITIALIZED_THIS;
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="ByteCode Class">
private final static class ByteCode {
Label label;
Instruction instruction;
String name;
int offset; // NOTE: If you change 'offset', clear out 'name'.
int endOffset;
AstCode code;
Object operand;
Object secondOperand;
int popCount = -1;
int pushCount;
ByteCode next;
ByteCode previous;
FrameValue type;
StackSlot[] stackBefore;
VariableSlot[] variablesBefore;
List<Variable> storeTo;
public final String name() {
if (name == null) {
name = format("#%1$04d", offset);
}
return name;
}
public final String makeLabelName() {
return format("Label_%1$04d", offset);
}
public final Frame getFrameBefore() {
final FrameValue[] stackValues;
final FrameValue[] variableValues;
if (stackBefore.length == 0) {
stackValues = FrameValue.EMPTY_VALUES;
}
else {
stackValues = new FrameValue[stackBefore.length];
for (int i = 0; i < stackBefore.length; i++) {
stackValues[i] = stackBefore[i].value;
}
}
if (variablesBefore.length == 0) {
variableValues = FrameValue.EMPTY_VALUES;
}
else {
variableValues = new FrameValue[variablesBefore.length];
for (int i = 0; i < variablesBefore.length; i++) {
variableValues[i] = variablesBefore[i].value;
}
}
return new Frame(FrameType.New, variableValues, stackValues);
}
public final boolean isVariableDefinition() {
return code == AstCode.Store ||
code == AstCode.Inc;
}
@Override
@SuppressWarnings("ConstantConditions")
public final String toString() {
final StringBuilder sb = new StringBuilder();
//
// Label
//
sb.append(name()).append(':');
if (label != null) {
sb.append('*');
}
//
// Name
//
sb.append(' ');
sb.append(code.getName());
if (operand != null) {
sb.append(' ');
if (operand instanceof Instruction) {
sb.append(format("#%1$04d", ((Instruction) operand).getOffset()));
}
else if (operand instanceof Instruction[]) {
for (final Instruction instruction : (Instruction[]) operand) {
sb.append(format("#%1$04d", instruction.getOffset()));
sb.append(' ');
}
}
else if (operand instanceof Label) {
sb.append(((Label) operand).getName());
}
else if (operand instanceof Label[]) {
for (final Label l : (Label[]) operand) {
sb.append(l.getName());
sb.append(' ');
}
}
else {
sb.append(operand);
}
}
if (stackBefore != null) {
sb.append(" StackBefore={");
for (int i = 0; i < stackBefore.length; i++) {
if (i != 0) {
sb.append(',');
}
final StackSlot slot = stackBefore[i];
final ByteCode[] definitions = slot.definitions;
for (int j = 0; j < definitions.length; j++) {
if (j != 0) {
sb.append('|');
}
sb.append(format("#%1$04d", definitions[j].offset));
}
}
sb.append('}');
}
if (storeTo != null && !storeTo.isEmpty()) {
sb.append(" StoreTo={");
for (int i = 0; i < storeTo.size(); i++) {
if (i != 0) {
sb.append(',');
}
sb.append(storeTo.get(i).getName());
}
sb.append('}');
}
if (variablesBefore != null) {
sb.append(" VariablesBefore={");
for (int i = 0; i < variablesBefore.length; i++) {
if (i != 0) {
sb.append(',');
}
final VariableSlot slot = variablesBefore[i];
if (slot.isUninitialized()) {
sb.append('?');
}
else {
final ByteCode[] definitions = slot.definitions;
for (int j = 0; j < definitions.length; j++) {
if (j != 0) {
sb.append('|');
}
sb.append(format("#%1$04d", definitions[j].offset));
}
}
}
sb.append('}');
}
return sb.toString();
}
}
// </editor-fold>
}