|
|
|
/* RemovePopAnalyzer Copyright (C) 1999 Jochen Hoenicke.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; see the file COPYING. If not, write to
|
|
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*
|
|
|
|
* $Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
package net.sf.jode.obfuscator.modules;
|
|
|
|
import net.sf.jode.bytecode.*;
|
|
|
|
import net.sf.jode.obfuscator.*;
|
|
|
|
import net.sf.jode.GlobalOptions;
|
|
|
|
|
|
|
|
import java.util.BitSet;
|
|
|
|
///#def COLLECTIONS java.util
|
|
|
|
import java.util.ListIterator;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.Stack;
|
|
|
|
///#enddef
|
|
|
|
|
|
|
|
public class RemovePopAnalyzer implements CodeTransformer, Opcodes {
|
|
|
|
public RemovePopAnalyzer() {
|
|
|
|
}
|
|
|
|
|
|
|
|
static class BlockInfo {
|
|
|
|
/* A bitset of stack entries at the beginning of the block,
|
|
|
|
* whose values should be never put put onto the stack.
|
|
|
|
* This array is shared with all other blocks that have
|
|
|
|
* a common predecessor.
|
|
|
|
*/
|
|
|
|
int[] poppedBefore;
|
|
|
|
|
|
|
|
/* A bitset of instructions, that should be removed, i.e. their
|
|
|
|
* parameters should just get popped.
|
|
|
|
*/
|
|
|
|
int[] removedInstrs;
|
|
|
|
|
|
|
|
ArrayList predecessors;
|
|
|
|
|
|
|
|
BlockInfo(int[] popped, int[] removed) {
|
|
|
|
this.poppedEntries = popped;
|
|
|
|
this.removedInstrs = removed;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean isPopped(int pos) {
|
|
|
|
return (poppedEntries[pos >> 5] & (1 << (pos & 31))) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean isRemoved(int pos) {
|
|
|
|
return (removedInstrs[pos >> 5] & (1 << (pos & 31))) != 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public BlockInfo analyzeBlock(Block block, BlockInfo oldInfo) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method propagates pops through a dup instruction, eventually
|
|
|
|
* generating new code and a new set of popped instructions.
|
|
|
|
*
|
|
|
|
* @param opcode the opcode of the dup instruction.
|
|
|
|
* @param newInstruction a list where the new instructions should
|
|
|
|
* be added to the front.
|
|
|
|
* @param stackDepth the stack depth after the dup is executed.
|
|
|
|
* @param poppedEntries the stack slots that should be popped at
|
|
|
|
* the end of this dup.
|
|
|
|
* @return The stack slots that should be popped at the start of
|
|
|
|
* the dup.
|
|
|
|
*/
|
|
|
|
byte movePopsThroughDup(int opcode,
|
|
|
|
List newInstructions,
|
|
|
|
int poppedEntries) {
|
|
|
|
int count = (opcode - opc_dup)/3+1;
|
|
|
|
int depth = (opcode - opc_dup)%3;
|
|
|
|
|
|
|
|
/* Calculate which entries can be popped before this instruction,
|
|
|
|
* and update opcode.
|
|
|
|
*/
|
|
|
|
int newPopped =
|
|
|
|
(((poppedEntries + 1) << depth) - 1) & (poppedEntries >> count);
|
|
|
|
int mask = ((1 << count) - 1);
|
|
|
|
int poppedDeep = poppedEntries & mask;
|
|
|
|
int poppedTop = (poppedEntries >> (depth + count)) & mask;
|
|
|
|
boolean swapAfterDup = false;
|
|
|
|
boolean swapBeforeDup = false;
|
|
|
|
|
|
|
|
for (int i = count+depth; i > depth; i--) {
|
|
|
|
if ((newPopped & (1 << i)) != 0)
|
|
|
|
depth--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// adjust the depth
|
|
|
|
for (int i = depth; i > 0; i--) {
|
|
|
|
if ((newPopped & (1 << i)) != 0)
|
|
|
|
depth--;
|
|
|
|
}
|
|
|
|
|
|
|
|
// adjust the count and poppedDeep/3
|
|
|
|
if ((poppedDeep & poppedTop & 1) != 0) {
|
|
|
|
count--;
|
|
|
|
poppedDeep >>= 1;
|
|
|
|
poppedTop >>= 1;
|
|
|
|
mask >>= 1;
|
|
|
|
} else if ((poppedDeep & poppedTop & 2) != 0) {
|
|
|
|
count--;
|
|
|
|
poppedDeep &= 1;
|
|
|
|
poppedTop &= 1;
|
|
|
|
mask &= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (poppedDeep == mask
|
|
|
|
|| (depth == 0 && poppedTop == mask)) {
|
|
|
|
// dup was not necessary
|
|
|
|
return newPopped;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now (poppedDeep & poppedTop) == 0 */
|
|
|
|
|
|
|
|
if (poppedTop > 0) {
|
|
|
|
/* Insert the pop for the top elements, we add
|
|
|
|
* the dup later in front of these instructions.
|
|
|
|
*/
|
|
|
|
if (poppedTop == 3) {
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop2));
|
|
|
|
} else {
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop));
|
|
|
|
if (count == 2 && poppedTop == 1)
|
|
|
|
swapAfterDup = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (poppedDeep != 0) {
|
|
|
|
if (poppedDeep == 2) {
|
|
|
|
/* We swap before and after dupping to get to
|
|
|
|
* poppedDeep = 1 case.
|
|
|
|
*/
|
|
|
|
swapAfterDup = !swapAfterDup;
|
|
|
|
swapBeforeDup = true;
|
|
|
|
}
|
|
|
|
/* The bottom most value is popped; decrease count
|
|
|
|
* and increase depth, so that it won't be created
|
|
|
|
* in the first place.
|
|
|
|
*/
|
|
|
|
depth++;
|
|
|
|
count--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now all pops are resolved */
|
|
|
|
/* Do a dup with count and depth now. */
|
|
|
|
|
|
|
|
if (swapAfterDup)
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_swap));
|
|
|
|
|
|
|
|
if (depth < 3) {
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop - 3 +
|
|
|
|
depth + 3 * count));
|
|
|
|
} else {
|
|
|
|
// I hope that this will almost never happen.
|
|
|
|
// depth = 3, count = 1;
|
|
|
|
// Note that instructions are backwards.
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop2)); //DABCD<
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_dup2_x2)); //DABCDAB<
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop)); //DCDAB<
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_dup_x2)); //DCDABD<
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_pop)); //DCABD<
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_dup2_x2)); //DCABDC<
|
|
|
|
swappedBeforeDup = !swappedBeforeDup; //ABDC<
|
|
|
|
}
|
|
|
|
|
|
|
|
if (swapBeforeDup)
|
|
|
|
newInstructions.addFirst
|
|
|
|
(Instruction.forOpcode(opc_swap));
|
|
|
|
return newPopped;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method analyzes a block from end to start and removes the
|
|
|
|
* pop instructions together with their pushes. It propagates pops
|
|
|
|
* to the front removing various instructions on the fly, which may
|
|
|
|
* generate new pops for their operands and so on.
|
|
|
|
*
|
|
|
|
* @param block the block of code.
|
|
|
|
* @param poppedEntries the stack slots that should be popped at
|
|
|
|
* the end of this block.
|
|
|
|
* @return the stack slots that should be popped at the start of
|
|
|
|
* the block.
|
|
|
|
*/
|
|
|
|
BitSet movePopsToFront(Block block, BitSet poppedEntries) {
|
|
|
|
/* Calculate stack height at end of block. */
|
|
|
|
Instruction[] oldInstrs = block.getInstructions();
|
|
|
|
LinkedList newInstructions = new LinkedList();
|
|
|
|
|
|
|
|
int instrNr = oldInstrs.length;
|
|
|
|
int stackDepth = block.getStackHeight() + block.getStackDelta();
|
|
|
|
while (instrNr > 0) {
|
|
|
|
Instruction instr = oldInstrs[--instrNr];
|
|
|
|
if (instr.getOpcode() == opc_nop)
|
|
|
|
continue;
|
|
|
|
if (instr.getOpcode() == opc_pop) {
|
|
|
|
popsAtEnd.set(stackDepth++);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (instr.getOpcode() == opc_pop2) {
|
|
|
|
popsAtEnd.set(stackDepth++);
|
|
|
|
popsAtEnd.set(stackDepth++);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
instr.getStackPopPush(poppush);
|
|
|
|
|
|
|
|
/* Check if this instr pushes a popped Entry. */
|
|
|
|
boolean push_a_popped = false;
|
|
|
|
boolean push_all_popped = true;
|
|
|
|
for (int j=0; j < poppush[1]; j++) {
|
|
|
|
if (poppedEntries.get(j))
|
|
|
|
push_a_popped = true;
|
|
|
|
else
|
|
|
|
push_all_popped = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!push_a_popped) {
|
|
|
|
// Normal case:
|
|
|
|
// add the instruction and adjust stack depth.
|
|
|
|
newInstructions.addFirst(instr);
|
|
|
|
stackDepth += poppush[0] - poppush[1];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We push an entry, that gets popped later */
|
|
|
|
int opcode = instr.getOpcode();
|
|
|
|
switch (opcode) {
|
|
|
|
case opc_dup:
|
|
|
|
case opc_dup_x1:
|
|
|
|
case opc_dup_x2:
|
|
|
|
case opc_dup2:
|
|
|
|
case opc_dup2_x1:
|
|
|
|
case opc_dup2_x2: {
|
|
|
|
int popped = 0;
|
|
|
|
for (int j = poppush[1] ; j > 0; j--) {
|
|
|
|
popped <<= 1;
|
|
|
|
if (poppedEntries.get(--stackDepth)) {
|
|
|
|
popped |= 1;
|
|
|
|
poppedEntries.clear(stackDepth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
popped = movePopsThroughDup(opcode, newInstructions,
|
|
|
|
popped);
|
|
|
|
for (int j=0; j < poppush[1]; j++) {
|
|
|
|
if ((popped & 1) != 0)
|
|
|
|
poppedEntries.set(stackDepth);
|
|
|
|
stackDepth++;
|
|
|
|
popped >>=1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case opc_swap:
|
|
|
|
if (!push_all_popped) {
|
|
|
|
// swap the popped status
|
|
|
|
if (poppedEntries.get(stackDepth - 1)) {
|
|
|
|
poppedEntries.clear(stackDepth - 1);
|
|
|
|
poppedEntries.set(stackDepth - 2);
|
|
|
|
} else {
|
|
|
|
poppedEntries.set(stackDepth - 1);
|
|
|
|
poppedEntries.clear(stackDepth - 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case opc_ldc2_w:
|
|
|
|
case opc_lload: case opc_dload:
|
|
|
|
case opc_i2l: case opc_i2d:
|
|
|
|
case opc_f2l: case opc_f2d:
|
|
|
|
case opc_ldc:
|
|
|
|
case opc_iload: case opc_fload: case opc_aload:
|
|
|
|
case opc_new:
|
|
|
|
case opc_lneg: case opc_dneg:
|
|
|
|
case opc_l2d: case opc_d2l:
|
|
|
|
case opc_laload: case opc_daload:
|
|
|
|
case opc_ineg: case opc_fneg:
|
|
|
|
case opc_i2f: case opc_f2i:
|
|
|
|
case opc_i2b: case opc_i2c: case opc_i2s:
|
|
|
|
case opc_newarray: case opc_anewarray:
|
|
|
|
case opc_arraylength:
|
|
|
|
case opc_instanceof:
|
|
|
|
case opc_lshl: case opc_lshr: case opc_lushr:
|
|
|
|
case opc_iaload: case opc_faload: case opc_aaload:
|
|
|
|
case opc_baload: case opc_caload: case opc_saload:
|
|
|
|
case opc_iadd: case opc_fadd:
|
|
|
|
case opc_isub: case opc_fsub:
|
|
|
|
case opc_imul: case opc_fmul:
|
|
|
|
case opc_idiv: case opc_fdiv:
|
|
|
|
case opc_irem: case opc_frem:
|
|
|
|
case opc_iand: case opc_ior : case opc_ixor:
|
|
|
|
case opc_ishl: case opc_ishr: case opc_iushr:
|
|
|
|
case opc_fcmpl: case opc_fcmpg:
|
|
|
|
case opc_l2i: case opc_l2f:
|
|
|
|
case opc_d2i: case opc_d2f:
|
|
|
|
case opc_ladd: case opc_dadd:
|
|
|
|
case opc_lsub: case opc_dsub:
|
|
|
|
case opc_lmul: case opc_dmul:
|
|
|
|
case opc_ldiv: case opc_ddiv:
|
|
|
|
case opc_lrem: case opc_drem:
|
|
|
|
case opc_land: case opc_lor : case opc_lxor:
|
|
|
|
case opc_lcmp:
|
|
|
|
case opc_dcmpl: case opc_dcmpg:
|
|
|
|
case opc_getstatic:
|
|
|
|
case opc_getfield:
|
|
|
|
case opc_multianewarray:
|
|
|
|
|
|
|
|
/* The simple instructions, that can be removed. */
|
|
|
|
if (!push_all_popped)
|
|
|
|
throw new InternalError("pop half of a long");
|
|
|
|
if (poppush[0] < poppush[1]) {
|
|
|
|
for (int j=0; j < poppush[0] - poppush[1]; j++)
|
|
|
|
poppedEntries.set(stackDepth++);
|
|
|
|
} else if (poppush[0] < poppush[1]) {
|
|
|
|
for (int j=0; j < poppush[0] - poppush[1]; j++)
|
|
|
|
poppedEntries.clear(--stackDepth);
|
|
|
|
}
|
|
|
|
|
|
|
|
case opc_invokevirtual:
|
|
|
|
case opc_invokespecial:
|
|
|
|
case opc_invokestatic:
|
|
|
|
case opc_invokeinterface:
|
|
|
|
case opc_checkcast:
|
|
|
|
|
|
|
|
/* These instructions can't be removed, since
|
|
|
|
* they have side effects.
|
|
|
|
*/
|
|
|
|
if (!push_all_popped)
|
|
|
|
throw new InternalError("pop half of a long");
|
|
|
|
if (poppush[1] == 1) {
|
|
|
|
poppedEntries.clear(--stackDepth);
|
|
|
|
newInstructions
|
|
|
|
.addFirst(Instruction.forOpcode(opc_pop));
|
|
|
|
} else {
|
|
|
|
poppedEntries.clear(--stackDepth);
|
|
|
|
poppedEntries.clear(--stackDepth);
|
|
|
|
newInstructions
|
|
|
|
.addFirst(Instruction.forOpcode(opc_pop2));
|
|
|
|
}
|
|
|
|
newInstructions.addFirst(instr);
|
|
|
|
default:
|
|
|
|
throw new InternalError("Unexpected opcode!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
blocks[i].setCode((Instruction[]) newInstructions
|
|
|
|
.toArray(new Instruction[newInstructions.size()]),
|
|
|
|
blocks[i].getSuccs());
|
|
|
|
return poppedEntries;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method analyzes a block from start to end and inserts the
|
|
|
|
* pop instructions at the right places. It is used if a pop couldn't
|
|
|
|
* be removed for some reason.
|
|
|
|
*
|
|
|
|
* @param block the block of code.
|
|
|
|
* @param poppedEntries the stack slots that should be popped at
|
|
|
|
* the end of this block.
|
|
|
|
* @return the stack slots that should be popped at the start of
|
|
|
|
* the block.
|
|
|
|
*/
|
|
|
|
void movePopsToTail(Block block, BitSet poppedEntries) {
|
|
|
|
/* Calculate stack height at end of block. */
|
|
|
|
Instruction[] oldInstrs = block.getInstructions();
|
|
|
|
ArrayList newInstructions = new ArrayList();
|
|
|
|
|
|
|
|
int instrNr = oldInstrs.length;
|
|
|
|
int stackDepth = block.getStackHeight();
|
|
|
|
for (instrNr = 0; instrNr < oldInstrs.length; instrNr++) {
|
|
|
|
while (poppedEntries.get(stackDepth-1)) {
|
|
|
|
poppedEntries.clear(--stackDepth);
|
|
|
|
/* XXX opc_pop2?*/
|
|
|
|
newInstructions.add(Instruction.forOpcode(opc_pop));
|
|
|
|
}
|
|
|
|
|
|
|
|
Instruction instr = oldInstrs[--instrNr];
|
|
|
|
instr.getStackPopPush(poppush);
|
|
|
|
/* XXX handle pops inside a opc_dup */
|
|
|
|
}
|
|
|
|
block.setCode((Instruction[]) newInstructions
|
|
|
|
.toArray(new Instruction[newInstructions.size()]),
|
|
|
|
block.getSuccs());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void transformCode(BasicBlocks bb) {
|
|
|
|
if (bb.getStartBlock() == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BlockInfo[] infos = calcBlockInfos(bb);
|
|
|
|
|
|
|
|
int poppush[] = new int[2];
|
|
|
|
boolean poppedEntries[] = new boolean[bb.getMaxStack()];
|
|
|
|
Block[] blocks = bb.getBlocks();
|
|
|
|
for (int i = 0; i < blocks.length; i++) {
|
|
|
|
blocks[i].setCode((Instruction[]) newInstructions
|
|
|
|
.toArray(oldInstrs), blocks[i].getSuccs());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|