diff --git a/jode/jode/obfuscator/modules/.cvsignore b/jode/jode/obfuscator/modules/.cvsignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/jode/jode/obfuscator/modules/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/jode/jode/obfuscator/modules/ConstantAnalyzer.java.in b/jode/jode/obfuscator/modules/ConstantAnalyzer.java.in new file mode 100644 index 0000000..5f4c818 --- /dev/null +++ b/jode/jode/obfuscator/modules/ConstantAnalyzer.java.in @@ -0,0 +1,1707 @@ +/* ConstantAnalyzer 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 jode.obfuscator.modules; + +import jode.AssertError; +import jode.GlobalOptions; +import jode.bytecode.*; +import jode.jvm.InterpreterException; +import jode.obfuscator.*; + +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; +import java.util.BitSet; + +import @COLLECTIONS@.Arrays; +import @COLLECTIONS@.Collection; +import @COLLECTIONS@.HashSet; +import @COLLECTIONS@.Set; +import @COLLECTIONS@.HashMap; +import @COLLECTIONS@.Map; +import @COLLECTIONS@.Iterator; +import @COLLECTIONS@.ListIterator; + +/** + * Analyze the code, assuming every field that is not yet written to + * is constant. This may imply that some code is dead code. + * + * @author Jochen Hoenicke + */ +public class ConstantAnalyzer implements Opcodes, CodeAnalyzer { + + BytecodeInfo bytecode; + + private static ConstantRuntimeEnvironment runtime + = new ConstantRuntimeEnvironment(); + + private final static int CMP_EQ = 0; + private final static int CMP_NE = 1; + private final static int CMP_LT = 2; + private final static int CMP_GE = 3; + private final static int CMP_GT = 4; + private final static int CMP_LE = 5; + private final static int CMP_GREATER_MASK + = (1 << CMP_GT)|(1 << CMP_GE)|(1 << CMP_NE); + private final static int CMP_LESS_MASK + = (1 << CMP_LT)|(1 << CMP_LE)|(1 << CMP_NE); + private final static int CMP_EQUAL_MASK + = (1 << CMP_GE)|(1 << CMP_LE)|(1 << CMP_EQ); + + final static int CONSTANT = 0x02; + final static int CONSTANTFLOW = 0x04; + final static int RETASTORE = 0x08; + final static int RETURNINGJSR = 0x10; + + private interface ConstantListener { + public void constantChanged(); + } + + private final static class JSRTargetInfo implements Cloneable { + Instruction jsrTarget; + BitSet usedLocals; + + /** + * The dependent entries, that want to know if the bit set changed. + * This is either a StackLocalInfo (the ret info) or a single + * JSRTargetInfo or a Collection of JSRTargetInfos. + */ + Object dependent; + + public JSRTargetInfo(Instruction target) { + jsrTarget = target; + usedLocals = new BitSet(); + } + + public JSRTargetInfo copy() { + try { + JSRTargetInfo result = (JSRTargetInfo) clone(); + result.usedLocals = (BitSet) usedLocals.clone(); + addDependent(result); + return result; + } catch (CloneNotSupportedException ex) { + throw new IncompatibleClassChangeError(ex.getMessage()); + } + } + + private void addDependent(JSRTargetInfo result) { + if (dependent == null || dependent == result) + dependent = result; + else if (dependent instanceof JSRTargetInfo) { + Set newDeps = new HashSet(); + newDeps.add(dependent); + newDeps.add(result); + } else if (dependent instanceof Collection) { + ((Collection) dependent).add(result); + } + } + + public void setRetInfo(StackLocalInfo retInfo) { + dependent = retInfo; + } + + public boolean uses(int localSlot) { + return usedLocals.get(localSlot); + } + + public void addUsed(int localSlot) { + if (usedLocals.get(localSlot)) + return; + usedLocals.set(localSlot); + + if (dependent instanceof StackLocalInfo) + ((StackLocalInfo) dependent).enqueue(); + else if (dependent instanceof JSRTargetInfo) + ((JSRTargetInfo) dependent).addUsed(localSlot); + else if (dependent instanceof Collection) { + Iterator iter = ((Collection) dependent).iterator(); + while (iter.hasNext()) { + JSRTargetInfo dep = (JSRTargetInfo) iter.next(); + dep.addUsed(localSlot); + } + } + } + + public void merge(JSRTargetInfo o) { + o.addDependent(this); + for (int slot = 0; slot < o.usedLocals.size(); slot++) { + if (o.usedLocals.get(slot)) + addUsed(slot); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer(String.valueOf(jsrTarget)); + if (dependent instanceof StackLocalInfo) + sb.append("->").append(((StackLocalInfo) dependent).instr); + return sb.append(usedLocals) + .append('_').append(hashCode()).toString(); + } + } + + private static class ConstValue implements ConstantListener { + public final static Object VOLATILE = new Object(); + /** + * The constant value, VOLATILE if value is not constant. + * This may also be an instance of JSRTargetInfo + */ + Object value; + /** + * The number of slots this value takes on the stack. + */ + int stackSize; + /** + * The constant listeners, that want to be informed if this is + * no longer constant. + */ + Set listeners; + + public ConstValue(Object constant) { + value = constant; + stackSize = (constant instanceof Double + || constant instanceof Long) ? 2 : 1; + listeners = new HashSet(); + } + + public ConstValue(ConstValue constant) { + value = constant.value; + stackSize = constant.stackSize; + listeners = new HashSet(); + constant.addConstantListener(this); + } + + public ConstValue(int stackSize) { + this.value = VOLATILE; + this.stackSize = stackSize; + } + + public ConstValue copy() { + return (value == VOLATILE) ? this + : new ConstValue(this); + } + + public void addConstantListener(ConstantListener l) { + listeners.add(l); + } + + public void removeConstantListener(ConstantListener l) { + listeners.remove(l); + } + + public void fireChanged() { + value = VOLATILE; + for (Iterator i = listeners.iterator(); i.hasNext(); ) + ((ConstantListener) i.next()).constantChanged(); + listeners = null; + } + + public void constantChanged() { + if (value != VOLATILE) + fireChanged(); + } + + /** + * Merge the other value into this value. + */ + public void merge(ConstValue other) { + if (this == other) + return; + + if (value == null ? other.value == null + : value.equals(other.value)) { + if (value != VOLATILE) { +// other.addConstantListener(this); + this.addConstantListener(other); + } + return; + } + + if (value instanceof JSRTargetInfo + && other.value instanceof JSRTargetInfo + && (((JSRTargetInfo) value).jsrTarget + == ((JSRTargetInfo) other.value).jsrTarget)) { + ((JSRTargetInfo) value).merge((JSRTargetInfo) other.value); + return; + } + + if (value != VOLATILE) + fireChanged(); +// if (other.value != VOLATILE) +// other.fireChanged(); + } + + public String toString() { + return value == VOLATILE ? "vol("+stackSize+")" : ""+value; + } + } + + private static class TodoQueue { + StackLocalInfo first; + } + + private static class StackLocalInfo implements ConstantListener { + ConstValue[] stack; + ConstValue[] locals; + Instruction instr; + ConstantInfo constInfo; + StackLocalInfo retInfo; + + StackLocalInfo nextOnQueue; + + /** + * The queue that should be notified, if the constant values of + * this instruction changes. We put ourself on this queue in that + * case. + */ + TodoQueue notifyQueue; + + public ConstValue copy(ConstValue value) { + return (value == null) ? null : value.copy(); + } + + private StackLocalInfo(ConstValue[] stack, + ConstValue[] locals, + TodoQueue notifyQueue) { + this.stack = stack; + this.locals = locals; + this.notifyQueue = notifyQueue; + } + + public StackLocalInfo(int numLocals, + boolean isStatic, String methodTypeSig, + TodoQueue notifyQueue) { + + String[] paramTypes + = TypeSignature.getParameterTypes(methodTypeSig); + locals = new ConstValue[numLocals]; + stack = new ConstValue[0]; + this.notifyQueue = notifyQueue; + int slot = 0; + if (!isStatic) + locals[slot++] = new ConstValue(1); + for (int i=0; i< paramTypes.length; i++) { + int stackSize = TypeSignature.getTypeSize(paramTypes[i]); + locals[slot] = unknownValue[stackSize-1]; + slot += stackSize; + } + } + + public final void enqueue() { + if (nextOnQueue == null) { + this.nextOnQueue = notifyQueue.first; + notifyQueue.first = this; + } + } + + public void constantChanged() { + enqueue(); + } + + public StackLocalInfo poppush(int pops, ConstValue push) { + ConstValue[] newStack + = new ConstValue[stack.length - pops + push.stackSize]; + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + System.arraycopy(stack, 0, newStack, 0, stack.length-pops); + newStack[stack.length-pops] = push.copy(); + return new StackLocalInfo(newStack, newLocals, notifyQueue); + } + + public StackLocalInfo pop(int pops) { + ConstValue[] newStack + = new ConstValue[stack.length - pops]; + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + System.arraycopy(stack, 0, newStack, 0, stack.length-pops); + return new StackLocalInfo(newStack, newLocals, notifyQueue); + } + + public StackLocalInfo dup(int count, int depth) { + ConstValue[] newStack + = new ConstValue[stack.length + count]; + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + if (depth == 0) + System.arraycopy(stack, 0, newStack, 0, stack.length); + else { + int pos = stack.length - count - depth; + System.arraycopy(stack, 0, newStack, 0, pos); + for (int i=0; i < count; i++) + newStack[pos++] = copy(stack[stack.length-count + i]); + for (int i=0; i < depth; i++) + newStack[pos++] = copy(stack[stack.length-count-depth + i]); + } + for (int i=0; i < count; i++) + newStack[stack.length+i] = copy(stack[stack.length-count + i]); + return new StackLocalInfo(newStack, newLocals, notifyQueue); + } + + public StackLocalInfo swap() { + ConstValue[] newStack + = new ConstValue[stack.length]; + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + System.arraycopy(stack, 0, newStack, 0, stack.length - 2); + newStack[stack.length-2] = stack[stack.length-1].copy(); + newStack[stack.length-1] = stack[stack.length-2].copy(); + return new StackLocalInfo(newStack, newLocals, notifyQueue); + } + + public StackLocalInfo copy() { + ConstValue[] newStack = (ConstValue[]) stack.clone(); + ConstValue[] newLocals = (ConstValue[]) locals.clone(); + return new StackLocalInfo(newStack, newLocals, notifyQueue); + } + + public ConstValue getLocal(int slot) { + return locals[slot]; + } + + public ConstValue getStack(int depth) { + return stack[stack.length - depth]; + } + + public StackLocalInfo setLocal(int slot, ConstValue value) { + locals[slot] = value; + if (value != null && value.stackSize == 2) + locals[slot+1] = null; + for (int i=0; i< locals.length; i++) { + if (locals[i] != null + && locals[i].value instanceof JSRTargetInfo) { + JSRTargetInfo jsrInfo = (JSRTargetInfo)locals[i].value; + if (!jsrInfo.uses(slot)) { + jsrInfo = jsrInfo.copy(); + locals[i] = locals[i].copy(); + locals[i].value = jsrInfo; + jsrInfo.addUsed(slot); + } + } + } + for (int i=0; i< stack.length; i++) { + if (stack[i] != null + && stack[i].value instanceof JSRTargetInfo) { + JSRTargetInfo jsrInfo = (JSRTargetInfo)stack[i].value; + if (!jsrInfo.uses(slot)) { + jsrInfo = jsrInfo.copy(); + stack[i] = stack[i].copy(); + stack[i].value = jsrInfo; + jsrInfo.addUsed(slot); + } + } + } + return this; + } + + public StackLocalInfo mergeRetLocals(JSRTargetInfo jsrTargetInfo, + StackLocalInfo retInfo) { + for (int slot = 0; slot < locals.length; slot++) { + if (jsrTargetInfo.uses(slot)) + locals[slot] = retInfo.locals[slot]; + } + locals[retInfo.instr.getLocalSlot()] = null; + + for (int i=0; i< locals.length; i++) { + if (locals[i] != null + && locals[i].value instanceof JSRTargetInfo) { + JSRTargetInfo jsrInfo = (JSRTargetInfo) locals[i].value; + jsrInfo = jsrInfo.copy(); + locals[i] = locals[i].copy(); + locals[i].value = jsrInfo; + for (int slot = 0; slot < locals.length; slot++) { + if (jsrTargetInfo.uses(slot)) + jsrInfo.addUsed(slot); + } + } + } + for (int i=0; i< stack.length; i++) { + if (stack[i] != null + && stack[i].value instanceof JSRTargetInfo) { + JSRTargetInfo jsrInfo = (JSRTargetInfo)stack[i].value; + jsrInfo = jsrInfo.copy(); + stack[i] = stack[i].copy(); + stack[i].value = jsrInfo; + for (int slot = 0; slot < locals.length; slot++) { + if (jsrTargetInfo.uses(slot)) + jsrInfo.addUsed(slot); + } + } + } + return this; + } + + public void merge(StackLocalInfo other) { + for (int i=0; i < locals.length; i++) { + if (locals[i] != null) { + if (other.locals[i] == null) { + locals[i] = null; + enqueue(); + } else { + locals[i].merge(other.locals[i]); + } + } + } + if (stack.length != other.stack.length) + throw new jode.AssertError("stack length differs"); + for (int i=0; i < stack.length; i++) { + if ((other.stack[i] == null) != (stack[i] == null)) + throw new jode.AssertError("stack types differ"); + else if (stack[i] != null) + stack[i].merge(other.stack[i]); + } + } + + public String toString() { + return "Locals: "+Arrays.asList(locals) + +"Stack: "+Arrays.asList(stack)+ "Instr: "+instr; + } + } + + private static class ConstantInfo implements ConstantListener { + ConstantInfo() { + this(0, null); + } + + ConstantInfo(int flags) { + this(flags, null); + } + + ConstantInfo(int flags, Object constant) { + this.flags = flags; + this.constant = constant; + } + + int flags; + /** + * The constant, may be an Instruction for CONSTANTFLOW. + */ + Object constant; + + public void constantChanged() { + constant = null; + flags &= ~(CONSTANT | CONSTANTFLOW); + } + } + + private static ConstValue[] unknownValue = { + new ConstValue(1), new ConstValue(2) + }; + + private static ConstantInfo unknownConstInfo = new ConstantInfo(); + + public ConstantAnalyzer() { + } + + public void mergeInfo(Instruction instr, + StackLocalInfo info) { + if (instr.getTmpInfo() == null) { + instr.setTmpInfo(info); + info.instr = instr; + info.enqueue(); + } else + ((StackLocalInfo)instr.getTmpInfo()).merge(info); + } + + public Identifier canonizeReference(Instruction instr) { + Reference ref = instr.getReference(); + Identifier ident = Main.getClassBundle().getIdentifier(ref); + String clName = ref.getClazz(); + String realClazzName; + if (ident != null) { + ClassIdentifier clazz = (ClassIdentifier)ident.getParent(); + realClazzName = "L" + (clazz.getFullName() + .replace('.', '/')) + ";"; + } else { + /* We have to look at the ClassInfo's instead, to + * point to the right method. + */ + ClassInfo clazz; + if (clName.charAt(0) == '[') { + /* Arrays don't define new methods (well clone(), + * but that can be ignored). + */ + clazz = ClassInfo.javaLangObject; + } else { + clazz = ClassInfo.forName + (clName.substring(1, clName.length()-1) + .replace('/','.')); + } + if (instr.getOpcode() >= opc_invokevirtual) { + while (clazz != null + && clazz.findMethod(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } else { + while (clazz != null + && clazz.findField(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } + + if (clazz == null) { + GlobalOptions.err.println("WARNING: Can't find reference: " + +ref); + realClazzName = clName; + } else + realClazzName = "L" + clazz.getName().replace('.', '/') + ";"; + } + if (!realClazzName.equals(ref.getClazz())) { + ref = Reference.getReference(realClazzName, + ref.getName(), ref.getType()); + instr.setReference(ref); + } + return ident; + } + + public void handleReference(Reference ref, boolean isVirtual) { + Main.getClassBundle().reachableReference(ref, isVirtual); + } + + public void handleClass(String clName) { + int i = 0; + while (i < clName.length() && clName.charAt(i) == '[') + i++; + if (i < clName.length() && clName.charAt(i) == 'L') { + clName = clName.substring(i+1, clName.length()-1); + Main.getClassBundle().reachableClass(clName); + } + } + + public void handleOpcode(StackLocalInfo info, Identifier fieldListener) { + Instruction instr = info.instr; + info.constInfo = unknownConstInfo; + + int opcode = instr.getOpcode(); + Handler[] handlers = bytecode.getExceptionHandlers(); + for (int i=0; i< handlers.length; i++) { + if (handlers[i].start.getAddr() <= instr.getAddr() + && handlers[i].end.getAddr() >= instr.getAddr()) + mergeInfo(handlers[i].catcher, + info.poppush(info.stack.length, unknownValue[0])); + } + ConstValue result; + switch (opcode) { + case opc_nop: + mergeInfo(instr.getNextByAddr(), info.pop(0)); + break; + + case opc_ldc: + case opc_ldc2_w: + result = new ConstValue(instr.getConstant()); + mergeInfo(instr.getNextByAddr(), info.poppush(0, result)); + break; + + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + result = info.getLocal(instr.getLocalSlot()); + if (result == null) { + dumpStackLocalInfo(); + System.err.println(info); + System.err.println(instr); + } + if (result.value != ConstValue.VOLATILE) { + info.constInfo = new ConstantInfo(CONSTANT, result.value); + result.addConstantListener(info.constInfo); + } + mergeInfo(instr.getNextByAddr(), + info.poppush(0, result) + .setLocal(instr.getLocalSlot(), result.copy())); + break; + case opc_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: { +// ConstValue array = info.getStack(2); +// ConstValue index = info.getStack(1); +// ConstValue newValue = null; +// if (index.value != index.ConstValue.VOLATILE +// && array.value != array.ConstValue.VOLATILE +// && array.value != null) { +// int indexVal = ((Integer) index.value).intValue(); +// Object content; +// switch(opcode) { +// case opc_baload: +// content = new Integer +// (array.value instanceof byte[] +// ? ((byte[])array.value)[indexVal] +// : ((boolean[])array.value)[indexVal] ? 1 : 0); +// case opc_caload: +// content = new Integer(((char[])array.value)[indexVal]); +// break; +// case opc_saload: +// content = new Integer(((short[])array.value)[indexVal]); +// break; +// case opc_iaload: +// case opc_laload: +// case opc_faload: +// case opc_daload: +// case opc_aaload: +// content = Array.get(array.value, indexVal); +// break; +// default: +// throw new jode.AssertError("Can't happen."); +// } +// result = new ConstValue(content); +// array.addConstantListener(result); +// index.addConstantListener(result); +// } else { + result = unknownValue[(opcode == opc_laload + || opcode == opc_daload) ? 1 : 0]; +// } + mergeInfo(instr.getNextByAddr(), info.poppush(2, result)); + break; + } + case opc_istore: case opc_fstore: case opc_astore: { + result = info.getStack(1); + if (result.value instanceof JSRTargetInfo) + info.constInfo.flags |= RETASTORE; + mergeInfo(instr.getNextByAddr(), + info.pop(1).setLocal(instr.getLocalSlot(), result)); + break; + } + case opc_lstore: case opc_dstore: { + mergeInfo(instr.getNextByAddr(), + info.pop(2).setLocal(instr.getLocalSlot(), info.getStack(2))); + break; + } + case opc_iastore: case opc_lastore: + case opc_fastore: case opc_dastore: case opc_aastore: + case opc_bastore: case opc_castore: case opc_sastore: { + int size = (opcode == opc_lastore + || opcode == opc_dastore) ? 2 : 1; + mergeInfo(instr.getNextByAddr(), info.pop(2+size)); + break; + } + case opc_pop: + mergeInfo(instr.getNextByAddr(), info.pop(1)); + break; + case opc_pop2: + mergeInfo(instr.getNextByAddr(), info.pop(2)); + break; + + case opc_dup: case opc_dup_x1: case opc_dup_x2: + case opc_dup2: case opc_dup2_x1: case opc_dup2_x2: + mergeInfo(instr.getNextByAddr(), + info.dup((opcode - (opc_dup - 3)) / 3, + (opcode - (opc_dup - 3)) % 3)); + break; + case opc_swap: + mergeInfo(instr.getNextByAddr(), info.swap()); + break; + + case opc_iadd: case opc_ladd: case opc_fadd: case opc_dadd: + case opc_isub: case opc_lsub: case opc_fsub: case opc_dsub: + case opc_imul: case opc_lmul: case opc_fmul: case opc_dmul: + case opc_idiv: case opc_ldiv: case opc_fdiv: case opc_ddiv: + case opc_irem: case opc_lrem: case opc_frem: case opc_drem: + case opc_iand: case opc_land: + case opc_ior : case opc_lor : + case opc_ixor: case opc_lxor: { + int size = 1 + (opcode - opc_iadd & 1); + ConstValue value1 = info.getStack(2*size); + ConstValue value2 = info.getStack(1*size); + boolean known = value1.value != ConstValue.VOLATILE + && value2.value != ConstValue.VOLATILE; + if (known) { + if ((opcode == opc_idiv + && ((Integer)value2.value).intValue() == 0) + || (opcode == opc_ldiv + && ((Long)value2.value).longValue() == 0)) + known = false; + } + if (known) { + Object newValue; + switch (opcode) { + case opc_iadd: + newValue = new Integer + (((Integer)value1.value).intValue() + + ((Integer)value2.value).intValue()); + break; + case opc_isub: + newValue = new Integer + (((Integer)value1.value).intValue() + - ((Integer)value2.value).intValue()); + break; + case opc_imul: + newValue = new Integer + (((Integer)value1.value).intValue() + * ((Integer)value2.value).intValue()); + break; + case opc_idiv: + newValue = new Integer + (((Integer)value1.value).intValue() + / ((Integer)value2.value).intValue()); + break; + case opc_irem: + newValue = new Integer + (((Integer)value1.value).intValue() + % ((Integer)value2.value).intValue()); + break; + case opc_iand: + newValue = new Integer + (((Integer)value1.value).intValue() + & ((Integer)value2.value).intValue()); + break; + case opc_ior: + newValue = new Integer + (((Integer)value1.value).intValue() + | ((Integer)value2.value).intValue()); + break; + case opc_ixor: + newValue = new Integer + (((Integer)value1.value).intValue() + ^ ((Integer)value2.value).intValue()); + break; + + case opc_ladd: + newValue = new Long + (((Long)value1.value).longValue() + + ((Long)value2.value).longValue()); + break; + case opc_lsub: + newValue = new Long + (((Long)value1.value).longValue() + - ((Long)value2.value).longValue()); + break; + case opc_lmul: + newValue = new Long + (((Long)value1.value).longValue() + * ((Long)value2.value).longValue()); + break; + case opc_ldiv: + newValue = new Long + (((Long)value1.value).longValue() + / ((Long)value2.value).longValue()); + break; + case opc_lrem: + newValue = new Long + (((Long)value1.value).longValue() + % ((Long)value2.value).longValue()); + break; + case opc_land: + newValue = new Long + (((Long)value1.value).longValue() + & ((Long)value2.value).longValue()); + break; + case opc_lor: + newValue = new Long + (((Long)value1.value).longValue() + | ((Long)value2.value).longValue()); + break; + case opc_lxor: + newValue = new Long + (((Long)value1.value).longValue() + ^ ((Long)value2.value).longValue()); + break; + + case opc_fadd: + newValue = new Float + (((Float)value1.value).floatValue() + + ((Float)value2.value).floatValue()); + break; + case opc_fsub: + newValue = new Float + (((Float)value1.value).floatValue() + - ((Float)value2.value).floatValue()); + break; + case opc_fmul: + newValue = new Float + (((Float)value1.value).floatValue() + * ((Float)value2.value).floatValue()); + break; + case opc_fdiv: + newValue = new Float + (((Float)value1.value).floatValue() + / ((Float)value2.value).floatValue()); + break; + case opc_frem: + newValue = new Float + (((Float)value1.value).floatValue() + % ((Float)value2.value).floatValue()); + break; + + case opc_dadd: + newValue = new Double + (((Double)value1.value).doubleValue() + + ((Double)value2.value).doubleValue()); + break; + case opc_dsub: + newValue = new Double + (((Double)value1.value).doubleValue() + - ((Double)value2.value).doubleValue()); + break; + case opc_dmul: + newValue = new Double + (((Double)value1.value).doubleValue() + * ((Double)value2.value).doubleValue()); + break; + case opc_ddiv: + newValue = new Double + (((Double)value1.value).doubleValue() + / ((Double)value2.value).doubleValue()); + break; + case opc_drem: + newValue = new Double + (((Double)value1.value).doubleValue() + % ((Double)value2.value).doubleValue()); + break; + default: + throw new jode.AssertError("Can't happen."); + } + info.constInfo = new ConstantInfo(CONSTANT, newValue); + result = new ConstValue(newValue); + result.addConstantListener(info.constInfo); + value1.addConstantListener(result); + value2.addConstantListener(result); + } else + result = unknownValue[size-1]; + mergeInfo(instr.getNextByAddr(), info.poppush(2*size, result)); + break; + } + case opc_ineg: case opc_lneg: case opc_fneg: case opc_dneg: { + int size = 1 + (opcode - opc_ineg & 1); + ConstValue value = info.getStack(size); + if (value.value != ConstValue.VOLATILE) { + Object newValue; + switch (opcode) { + case opc_ineg: + newValue = new Integer + (-((Integer)value.value).intValue()); + break; + case opc_lneg: + newValue = new Long + (- ((Long)value.value).longValue()); + break; + case opc_fneg: + newValue = new Float + (- ((Float)value.value).floatValue()); + break; + case opc_dneg: + newValue = new Double + (- ((Double)value.value).doubleValue()); + break; + default: + throw new jode.AssertError("Can't happen."); + } + info.constInfo = new ConstantInfo(CONSTANT, newValue); + result = new ConstValue(newValue); + result.addConstantListener(info.constInfo); + value.addConstantListener(result); + } else + result = unknownValue[size-1]; + mergeInfo(instr.getNextByAddr(), info.poppush(size, result)); + break; + } + case opc_ishl: case opc_lshl: + case opc_ishr: case opc_lshr: + case opc_iushr: case opc_lushr: { + int size = 1 + (opcode - opc_iadd & 1); + ConstValue value1 = info.getStack(size+1); + ConstValue value2 = info.getStack(1); + if (value1.value != ConstValue.VOLATILE + && value2.value != ConstValue.VOLATILE) { + Object newValue; + switch (opcode) { + case opc_ishl: + newValue = new Integer + (((Integer)value1.value).intValue() + << ((Integer)value2.value).intValue()); + break; + case opc_ishr: + newValue = new Integer + (((Integer)value1.value).intValue() + >> ((Integer)value2.value).intValue()); + break; + case opc_iushr: + newValue = new Integer + (((Integer)value1.value).intValue() + >>> ((Integer)value2.value).intValue()); + break; + + case opc_lshl: + newValue = new Long + (((Long)value1.value).longValue() + << ((Integer)value2.value).intValue()); + break; + case opc_lshr: + newValue = new Long + (((Long)value1.value).longValue() + >> ((Integer)value2.value).intValue()); + break; + case opc_lushr: + newValue = new Long + (((Long)value1.value).longValue() + >>> ((Integer)value2.value).intValue()); + break; + default: + throw new jode.AssertError("Can't happen."); + } + info.constInfo = new ConstantInfo(CONSTANT, newValue); + result = new ConstValue(newValue); + result.addConstantListener(info.constInfo); + value1.addConstantListener(result); + value2.addConstantListener(result); + } else + result = unknownValue[size-1]; + mergeInfo(instr.getNextByAddr(), info.poppush(size+1, result)); + break; + } + case opc_iinc: { + ConstValue local = info.getLocal(instr.getLocalSlot()); + if (local.value != ConstValue.VOLATILE) { + result = new ConstValue + (new Integer(((Integer)local.value).intValue() + + instr.getIncrement())); + local.addConstantListener(result); + } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), + info.copy().setLocal(instr.getLocalSlot(), result)); + break; + } + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_d2i: case opc_d2l: case opc_d2f: { + int insize = 1 + ((opcode - opc_i2l) / 3 & 1); + ConstValue stack = info.getStack(insize); + if (stack.value != ConstValue.VOLATILE) { + Object newVal; + switch(opcode) { + case opc_l2i: case opc_f2i: case opc_d2i: + newVal = new Integer(((Number)stack.value).intValue()); + break; + case opc_i2l: case opc_f2l: case opc_d2l: + newVal = new Long(((Number)stack.value).longValue()); + break; + case opc_i2f: case opc_l2f: case opc_d2f: + newVal = new Float(((Number)stack.value).floatValue()); + break; + case opc_i2d: case opc_l2d: case opc_f2d: + newVal = new Double(((Number)stack.value).doubleValue()); + break; + default: + throw new jode.AssertError("Can't happen."); + } + info.constInfo = new ConstantInfo(CONSTANT, newVal); + result = new ConstValue(newVal); + result.addConstantListener(info.constInfo); + stack.addConstantListener(result); + } else { + switch (opcode) { + case opc_i2l: case opc_f2l: case opc_d2l: + case opc_i2d: case opc_l2d: case opc_f2d: + result = unknownValue[1]; + break; + default: + result = unknownValue[0]; + } + } + mergeInfo(instr.getNextByAddr(), info.poppush(insize, result)); + break; + } + case opc_i2b: case opc_i2c: case opc_i2s: { + ConstValue stack = info.getStack(1); + if (stack.value != ConstValue.VOLATILE) { + int val = ((Integer)stack.value).intValue(); + switch(opcode) { + case opc_i2b: + val = (byte) val; + break; + case opc_i2c: + val = (char) val; + break; + case opc_i2s: + val = (short) val; + break; + } + Integer newVal = new Integer(val); + info.constInfo = new ConstantInfo(CONSTANT, newVal); + result = new ConstValue(newVal); + stack.addConstantListener(info.constInfo); + stack.addConstantListener(result); + } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), + info.poppush(1, result)); + break; + } + case opc_lcmp: { + ConstValue val1 = info.getStack(4); + ConstValue val2 = info.getStack(2); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + long value1 = ((Long) val1.value).longValue(); + long value2 = ((Long) val1.value).longValue(); + Integer newVal = new Integer(value1 == value2 ? 0 + : value1 < value2 ? -1 : 1); + info.constInfo = new ConstantInfo(CONSTANT, newVal); + result = new ConstValue(newVal); + result.addConstantListener(info.constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), info.poppush(4, result)); + break; + } + case opc_fcmpl: case opc_fcmpg: { + ConstValue val1 = info.getStack(2); + ConstValue val2 = info.getStack(1); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + float value1 = ((Float) val1.value).floatValue(); + float value2 = ((Float) val1.value).floatValue(); + Integer newVal = new Integer + (value1 == value2 ? 0 + : ( opcode == opc_fcmpg + ? (value1 < value2 ? -1 : 1) + : (value1 > value2 ? 1 : -1))); + info.constInfo = new ConstantInfo(CONSTANT, newVal); + result = new ConstValue(newVal); + result.addConstantListener(info.constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), info.poppush(2, result)); + break; + } + case opc_dcmpl: case opc_dcmpg: { + ConstValue val1 = info.getStack(4); + ConstValue val2 = info.getStack(2); + if (val1.value != ConstValue.VOLATILE + && val2.value != ConstValue.VOLATILE) { + double value1 = ((Double) val1.value).doubleValue(); + double value2 = ((Double) val1.value).doubleValue(); + Integer newVal = new Integer + (value1 == value2 ? 0 + : ( opcode == opc_dcmpg + ? (value1 < value2 ? -1 : 1) + : (value1 > value2 ? 1 : -1))); + info.constInfo = new ConstantInfo(CONSTANT, newVal); + result = new ConstValue(newVal); + result.addConstantListener(info.constInfo); + val1.addConstantListener(result); + val2.addConstantListener(result); + } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), info.poppush(4, result)); + break; + } + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_ifnull: case opc_ifnonnull: { + int size = 1; + ConstValue stacktop = info.getStack(1); + ConstValue other = null; + boolean known = stacktop.value != ConstValue.VOLATILE; + if (opcode >= opc_if_icmpeq && opcode <= opc_if_acmpne) { + other = info.getStack(2); + size = 2; + known &= other.value != ConstValue.VOLATILE; + } + if (known) { + stacktop.addConstantListener(info); + if (other != null) + other.addConstantListener(info); + + Instruction pc = instr.getNextByAddr(); + int opc_mask; + if (opcode >= opc_if_acmpeq) { + if (opcode >= opc_ifnull) { + opc_mask = stacktop.value == null + ? CMP_EQUAL_MASK : CMP_GREATER_MASK; + opcode -= opc_ifnull; + } else { + opc_mask = stacktop.value == other.value + ? CMP_EQUAL_MASK : CMP_GREATER_MASK; + opcode -= opc_if_acmpeq; + } + } else { + int value = ((Integer) stacktop.value).intValue(); + if (opcode >= opc_if_icmpeq) { + int val1 = ((Integer) other.value).intValue(); + opc_mask = (val1 == value ? CMP_EQUAL_MASK + : val1 < value ? CMP_LESS_MASK + : CMP_GREATER_MASK); + opcode -= opc_if_icmpeq; + } else { + opc_mask = (value == 0 ? CMP_EQUAL_MASK + : value < 0 ? CMP_LESS_MASK + : CMP_GREATER_MASK); + opcode -= opc_ifeq; + } + } + + if ((opc_mask & (1<= 0; i--) { + size += TypeSignature.getTypeSize(paramTypes[i]); + Object value = (argValues[i] = info.getStack(size)).value; + if (value != ConstValue.VOLATILE) + args[i] = value; + else + constant = false; + } + + if (opcode != opc_invokestatic) { + size++; + clsValue = info.getStack(size); + cls = clsValue.value; + if (cls == ConstValue.VOLATILE || cls == null) + constant = false; + } + String retType = TypeSignature.getReturnType(ref.getType()); + if (retType.equals("V")) { + handleReference(ref, opcode == opc_invokevirtual + || opcode == opc_invokeinterface); + mergeInfo(instr.getNextByAddr(), info.pop(size)); + break; + } + if (constant && !runtime.isWhite(retType)) { + /* This is not a valid constant type */ + constant = false; + } + Object methodResult = null; + if (constant) { + try { + methodResult = runtime.invokeMethod + (ref, opcode != opc_invokespecial, cls, args); + } catch (InterpreterException ex) { + constant = false; + if (jode.GlobalOptions.verboseLevel > 3) + GlobalOptions.err.println("Can't interpret "+ref+": " + + ex.getMessage()); + /* result is not constant */ + } catch (InvocationTargetException ex) { + constant = false; + if (jode.GlobalOptions.verboseLevel > 3) + GlobalOptions.err.println("Method "+ref + +" throwed exception: " + + ex.getTargetException()); + /* method always throws exception ? */ + } + } + ConstValue returnVal; + if (!constant) { + handleReference(ref, opcode == opc_invokevirtual + || opcode == opc_invokeinterface); + int retsize = TypeSignature.getTypeSize(retType); + returnVal = unknownValue[retsize - 1]; + } else { + info.constInfo = new ConstantInfo(CONSTANT, methodResult); + returnVal = new ConstValue(methodResult); + returnVal.addConstantListener(info.constInfo); + if (clsValue != null) + clsValue.addConstantListener(returnVal); + for (int i=0; i< argValues.length; i++) + argValues[i].addConstantListener(returnVal); + } + mergeInfo(instr.getNextByAddr(), info.poppush(size, returnVal)); + break; + } + + case opc_new: { + handleClass(instr.getClazzType()); + mergeInfo(instr.getNextByAddr(), info.poppush(0, unknownValue[0])); + break; + } + case opc_arraylength: { +// ConstValue array = info.getStack(1); +// if (array.value != ConstValue.VOLATILE +// && array.value != null) { +// Integer newValue = new Integer(Array.getLength(array.value)); +// info.constInfo = new ConstantInfo(CONSTANT, newValue); +// result = new ConstValue(newValue); +// result.addConstantListener(info.constInfo); +// array.addConstantListener(result); +// } else + result = unknownValue[0]; + mergeInfo(instr.getNextByAddr(), info.poppush(1, result)); + break; + } + case opc_checkcast: { + handleClass(instr.getClazzType()); + mergeInfo(instr.getNextByAddr(), info.pop(0)); + break; + } + case opc_instanceof: { + handleClass(instr.getClazzType()); + mergeInfo(instr.getNextByAddr(), info.poppush(1, unknownValue[0])); + break; + } + case opc_monitorenter: + case opc_monitorexit: + mergeInfo(instr.getNextByAddr(), info.pop(1)); + break; + case opc_multianewarray: + handleClass(instr.getClazzType()); + mergeInfo(instr.getNextByAddr(), + info.poppush(instr.getDimensions(), unknownValue[0])); + break; + default: + throw new IllegalArgumentException("Invalid opcode "+opcode); + } + } + + public void fieldNotConstant(FieldIdentifier fi) { + for (Iterator iter = bytecode.getInstructions().iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + if (instr.getOpcode() == opc_getfield + || instr.getOpcode() == opc_getstatic) { + Reference ref = instr.getReference(); + if (ref.getName().equals(fi.getName()) + && ref.getType().equals(fi.getType()) + && instr.getTmpInfo() != null) { + ((StackLocalInfo) instr.getTmpInfo()).enqueue(); + } + } + } + } + + public void dumpStackLocalInfo() { + for (Iterator iter = bytecode.getInstructions().iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + System.err.println(""+instr.getTmpInfo()); + System.err.println(instr.getDescription()); + } + } + + public void analyzeCode(MethodIdentifier methodIdent, + BytecodeInfo bytecode) { + this.bytecode = bytecode; + TodoQueue modifiedQueue = new TodoQueue(); + MethodInfo minfo = bytecode.getMethodInfo(); + + for (Iterator iter = bytecode.getInstructions().iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + instr.setTmpInfo(null); + } + + StackLocalInfo firstInfo = new StackLocalInfo + (bytecode.getMaxLocals(), minfo.isStatic(), minfo.getType(), + modifiedQueue); + firstInfo.instr = (Instruction) bytecode.getInstructions().get(0); + firstInfo.instr.setTmpInfo(firstInfo); + firstInfo.enqueue(); + while (modifiedQueue.first != null) { + StackLocalInfo info = modifiedQueue.first; + modifiedQueue.first = info.nextOnQueue; + info.nextOnQueue = null; + handleOpcode(info, methodIdent); + } + + Handler[] handlers = bytecode.getExceptionHandlers(); + for (int i=0; i< handlers.length; i++) { + if (handlers[i].catcher.getTmpInfo() != null + && handlers[i].type != null) + Main.getClassBundle().reachableClass(handlers[i].type); + } + for (Iterator iter = bytecode.getInstructions().iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + StackLocalInfo info = (StackLocalInfo) instr.getTmpInfo(); + if (info != null) { + if (info.constInfo.flags == 0) + instr.setTmpInfo(unknownConstInfo); + else + instr.setTmpInfo(info.constInfo); + } + } + } + + public static void replaceWith(ListIterator iter, Instruction instr, + Instruction replacement) { + switch(instr.getOpcode()) { + case opc_goto: + case opc_ldc: + case opc_ldc2_w: + case opc_iload: case opc_lload: + case opc_fload: case opc_dload: case opc_aload: + case opc_getstatic: + if (replacement == null) + iter.remove(); + else + iter.set(replacement); + return; + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_ifnull: case opc_ifnonnull: + case opc_arraylength: + case opc_getfield: + case opc_i2l: case opc_i2f: case opc_i2d: + case opc_f2i: case opc_f2l: case opc_f2d: + case opc_i2b: case opc_i2c: case opc_i2s: + case opc_ineg: case opc_fneg: + iter.set(new Instruction(opc_pop)); + break; + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + case opc_lcmp: + case opc_dcmpg: case opc_dcmpl: + 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: + iter.set(new Instruction(opc_pop2)); + /* fall through */ + case opc_fcmpg: case opc_fcmpl: + case opc_l2i: case opc_l2f: case opc_l2d: + case opc_d2i: case opc_d2l: case opc_d2f: + case opc_lneg: case opc_dneg: + 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_iaload: case opc_laload: + case opc_faload: case opc_daload: case opc_aaload: + case opc_baload: case opc_caload: case opc_saload: + iter.set(new Instruction(opc_pop2)); + break; + + case opc_lshl: case opc_lshr: case opc_lushr: + iter.set(new Instruction(opc_pop2)); + iter.add(new Instruction(opc_pop)); + break; + case opc_putstatic: + case opc_putfield: + if (TypeSignature + .getTypeSize(instr.getReference().getType()) == 2) { + iter.set(new Instruction(opc_pop2)); + if (instr.getOpcode() == opc_putfield) + iter.add(new Instruction(opc_pop)); + } else + iter.set(new Instruction(instr.getOpcode() == opc_putfield + ? opc_pop2 : opc_pop)); + break; + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: { + Reference ref = instr.getReference(); + String[] pt = TypeSignature.getParameterTypes(ref.getType()); + int arg = 0; + if (instr.getOpcode() != opc_invokestatic) + iter.set(new Instruction(opc_pop)); + else if (pt.length > 0) { + iter.set(new Instruction(TypeSignature.getTypeSize(pt[0]) + + opc_pop - 1)); + arg++; + } else { + if (replacement == null) + iter.remove(); + else + iter.set(replacement); + return; + } + + for (int i=arg; i < pt.length; i++) + iter.add(new Instruction(TypeSignature.getTypeSize(pt[i]) + + opc_pop - 1)); + } + } + if (replacement != null) + iter.add(replacement); + } + + public void appendJump(ListIterator iter, Instruction dest) { + /* Add a goto instruction after this opcode. */ + Instruction gotoInstr = new Instruction(Instruction.opc_goto); + gotoInstr.setSuccs(dest); + iter.add(gotoInstr); + } + + public void transformCode(BytecodeInfo bytecode) { + for (ListIterator iter = bytecode.getInstructions().listIterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + ConstantInfo info = (ConstantInfo) instr.getTmpInfo(); + instr.setTmpInfo(null); + + if (info == null + || (info.flags & (RETURNINGJSR | RETASTORE)) == RETASTORE) { + /* This instruction can't be reached logically, or + * it is a return value astore, that should be removed */ + iter.remove(); + } else if ((info.flags & CONSTANT) != 0) { + if (instr.getOpcode() > opc_ldc2_w) { + Instruction ldcInstr + = new Instruction(info.constant instanceof Long + || info.constant instanceof Double + ? opc_ldc2_w : opc_ldc); + ldcInstr.setConstant(info.constant); + replaceWith(iter, instr, ldcInstr); + if (GlobalOptions.verboseLevel > 2) + GlobalOptions.err.println + (bytecode + ": Replacing " + instr + + " with constant " + info.constant); + } + } else if ((info.flags & CONSTANTFLOW) != 0) { + Instruction pc = (Instruction) info.constant; + if (instr.getOpcode() >= opc_if_icmpeq + && instr.getOpcode() <= opc_if_acmpne) + iter.set(new Instruction(opc_pop2)); + else + iter.set(new Instruction(opc_pop)); + if (GlobalOptions.verboseLevel > 2) + GlobalOptions.err.println + (bytecode + ": Replacing " + instr + + " with goto " + pc.getAddr()); + while (iter.hasNext()) { + ConstantInfo nextinfo = (ConstantInfo) + ((Instruction) iter.next()).getTmpInfo(); + if (nextinfo != null) { + Instruction nextInstr = (Instruction) iter.previous(); + if (pc != nextInstr) + appendJump(iter, pc); + break; + } + /* Next instruction can't be reached logically */ + iter.remove(); + } + + } else { + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_nop: + iter.remove(); + break; + + case opc_jsr: + ConstantInfo jsrinfo = (ConstantInfo) + instr.getSingleSucc().getTmpInfo(); + if ((jsrinfo.flags & RETURNINGJSR) != 0) + /* A normal jsr, don't change it */ + break; + + /* This means, the jsr will never return. We + * replace it with a goto, the jsr will transform + * itself to remove the astore operation. + */ + Instruction gotoInstr = new Instruction(opc_goto); + gotoInstr.setSuccs(instr.getSingleSucc()); + iter.set(gotoInstr); + /* fall through */ + case opc_goto: + case opc_ifeq: case opc_ifne: + case opc_iflt: case opc_ifge: + case opc_ifgt: case opc_ifle: + case opc_ifnull: case opc_ifnonnull: + case opc_if_icmpeq: case opc_if_icmpne: + case opc_if_icmplt: case opc_if_icmpge: + case opc_if_icmpgt: case opc_if_icmple: + case opc_if_acmpeq: case opc_if_acmpne: + + while (iter.hasNext()) { + ConstantInfo nextinfo = (ConstantInfo) + ((Instruction) iter.next()).getTmpInfo(); + if (nextinfo != null + && ((nextinfo.flags & (RETURNINGJSR | RETASTORE)) + != RETASTORE)) { + + Instruction nextInstr + = (Instruction) iter.previous(); + if (instr.getSingleSucc() == nextInstr) { + /* put iter in sane state */ + iter.previous(); + iter.next(); + replaceWith(iter, instr, null); + } + break; + } + /* Next instruction can be removed */ + iter.remove(); + } + break; + + case opc_putstatic: + case opc_putfield: { + Reference ref = instr.getReference(); + FieldIdentifier fi = (FieldIdentifier) + Main.getClassBundle().getIdentifier(ref); + if (fi != null + && (Main.stripping & Main.STRIP_UNREACH) != 0 + && !fi.isReachable()) { + replaceWith(iter, instr, null); + } + break; + } + } + } + } + } +} diff --git a/jode/jode/obfuscator/modules/KeywordRenamer.java.in b/jode/jode/obfuscator/modules/KeywordRenamer.java.in new file mode 100644 index 0000000..1077156 --- /dev/null +++ b/jode/jode/obfuscator/modules/KeywordRenamer.java.in @@ -0,0 +1,81 @@ +/* KeywordRenamer 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 jode.obfuscator.modules; +import jode.obfuscator.*; +import @COLLECTIONS@.Collection; +import @COLLECTIONS@.Iterator; +import @COLLECTIONEXTRA@.UnsupportedOperationException; + +public class KeywordRenamer implements Renamer, OptionHandler { + String keywords[]; + Renamer backup; + + public KeywordRenamer() { + keywords = new String[] { + "if", "else", "for", "while", "throw", "return", + "class", "interface", "implements", "extends", + "instanceof", "new", + "int", "boolean", "long", "float", "double", "short", + "Object", "String", "Thread", + "public", "protected", "private", + "static", "synchronized", "strict", "transient", "abstract", + "volatile", "final", + /* Not really keywords, but very confusing anyway. */ + "Object", "String", "Runnable", "StringBuffer", "Vector" + }; + backup = new StrongRenamer(); + } + + public void setOption(String option, Collection values) { + if (option.startsWith("keywords")) { + keywords = (String[]) values.toArray(new String[values.size()]); + } else if (option.startsWith("backup")) { + if (values.size() != 1) + throw new IllegalArgumentException + ("Only one backup is allowed"); + backup = (Renamer) values.iterator().next(); + } else + throw new IllegalArgumentException("Invalid option `"+option+"'"); + } + + public Iterator generateNames(final Identifier ident) { + return new Iterator() { + int pos = 0; + Iterator backing = null; + + public boolean hasNext() { + return true; + } + public Object next() { + if (pos < keywords.length) + return keywords[pos++]; + + if (backing == null) + backing = backup.generateNames(ident); + + return backing.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/jode/jode/obfuscator/modules/LocalOptimizer.java.in b/jode/jode/obfuscator/modules/LocalOptimizer.java.in new file mode 100644 index 0000000..cb776fc --- /dev/null +++ b/jode/jode/obfuscator/modules/LocalOptimizer.java.in @@ -0,0 +1,941 @@ +/* LocalOptimizer 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 jode.obfuscator.modules; +import java.util.*; +import jode.bytecode.*; +import jode.obfuscator.*; +import jode.AssertError; +import jode.GlobalOptions; + +import @COLLECTIONS@.Iterator; +import @COLLECTIONS@.ListIterator; + +/** + * This class takes some bytecode and tries to minimize the number + * of locals used. It will also remove unnecessary stores. + * + * This class can only work on verified code. There should also be no + * deadcode, since the verifier doesn't check that deadcode behaves + * okay. + * + * This is done in two phases. First we determine which locals are + * the same, and which locals have a overlapping life time. In the + * second phase we will then redistribute the locals with a coloring + * graph algorithm. + * + * The idea for the first phase is: For each read we follow the + * instruction flow backward to find the corresponding writes. We can + * also merge with another control flow that has a different read, in + * this case we merge with that read, too. + * + * The tricky part is the subroutine handling. We follow the local + * that is used in a ret and find the corresponding jsr target (there + * must be only one, if the verifier should accept this class). While + * we do this we remember in the info of the ret, which locals are + * used in that subroutine. + * + * When we know the jsr target<->ret correlation, we promote from the + * nextByAddr of every jsr the locals that are accessed by the + * subroutine to the corresponding ret and the others to the jsr. Also + * we will promote all reads from the jsr targets to the jsr. + * + * If you think this might be to complicated, keep in mind that jsr's + * are not only left by the ret instructions, but also "spontanously" + * (by not reading the return address again). + */ +public class LocalOptimizer implements Opcodes, CodeTransformer { + + /** + * This class keeps track of which locals must be the same, which + * name and type each local (if there is a local variable table) and + * which other locals have an intersecting life time. + */ + class LocalInfo { + LocalInfo shadow = null; + + public LocalInfo getReal() { + LocalInfo real = this; + while (real.shadow != null) + real = real.shadow; + return real; + } + + String name; + String type; + Vector usingInstrs = new Vector(); + Vector conflictingLocals = new Vector(); + int size; + int newSlot = -1; + + LocalInfo() { + } + + LocalInfo(InstrInfo instr) { + usingInstrs.addElement(instr); + } + + void conflictsWith(LocalInfo l) { + if (shadow != null) { + getReal().conflictsWith(l); + } else { + l = l.getReal(); + if (!conflictingLocals.contains(l)) { + conflictingLocals.addElement(l); + l.conflictingLocals.addElement(this); + } + } + } + + void combineInto(LocalInfo l) { + if (shadow != null) { + getReal().combineInto(l); + return; + } + l = l.getReal(); + if (this == l) + return; + shadow = l; + if (shadow.name == null) { + shadow.name = name; + shadow.type = type; + } + Enumeration enum = usingInstrs.elements(); + while (enum.hasMoreElements()) { + InstrInfo instr = (InstrInfo) enum.nextElement(); + instr.local = l; + l.usingInstrs.addElement(instr); + } + } + + public int getFirstAddr() { + int minAddr = Integer.MAX_VALUE; + Enumeration enum = usingInstrs.elements(); + while (enum.hasMoreElements()) { + InstrInfo info = (InstrInfo) enum.nextElement(); + if (info.instr.getAddr() < minAddr) + minAddr = info.instr.getAddr(); + } + return minAddr; + } + } + + private static class TodoQueue { + public final InstrInfo LAST = new InstrInfo(); + InstrInfo first = LAST; + + public void add(InstrInfo info) { + if (info.nextTodo == null) { + /* only enqueue if not already on queue */ + info.nextTodo = first; + first = info; + } + } + + public boolean isEmpty() { + return first == LAST; + } + + public InstrInfo remove() { + if (first == LAST) + throw new NoSuchElementException(); + InstrInfo result = first; + first = result.nextTodo; + result.nextTodo = null; + return result; + } + } + + + /** + * This class contains information for each instruction. + */ + static class InstrInfo { + /** + * The next changed InstrInfo, or null, if this instr info did + * not changed. + */ + InstrInfo nextTodo; + + /** + * The LocalInfo that this instruction manipulates, or null + * if this is not an ret, iinc, load or store instruction. + */ + LocalInfo local; + /** + * For each slot, this contains the InstrInfo of one of the + * next Instruction, that may read from that slot, without + * prior writing. */ + InstrInfo[] nextReads; + + /** + * This only has a value for ret instructions. In that case + * this bitset contains all locals, that may be used between + * jsr and ret. + */ + BitSet usedBySub; + /** + * For each slot if get() is true, no instruction may read + * this slot, since it may contain different locals, depending + * on flow. + */ + LocalInfo[] lifeLocals; + /** + * If instruction is the destination of a jsr, this contains + * the single allowed ret instruction info, or null if there + * is no ret at all (or not yet detected). + */ + InstrInfo retInfo; + /** + * If this instruction is a ret, this contains the single + * allowed jsr target to which this ret belongs. + */ + InstrInfo jsrTargetInfo; + /** + * The Instruction of this info + */ + Instruction instr; + /** + * The next info in the chain. + */ + InstrInfo nextInfo; + } + + BytecodeInfo bc; + + TodoQueue changedInfos; + InstrInfo firstInfo; + Hashtable instrInfos; + boolean produceLVT; + int maxlocals; + + LocalInfo[] paramLocals; + + public LocalOptimizer() { + } + + + /** + * Merges the given vector to a new vector. Both vectors may + * be null in which case they are interpreted as empty vectors. + * The vectors will never changed, but the result may be one + * of the given vectors. + */ + Vector merge(Vector v1, Vector v2) { + if (v1 == null || v1.isEmpty()) + return v2; + if (v2 == null || v2.isEmpty()) + return v1; + Vector result = (Vector) v1.clone(); + Enumeration enum = v2.elements(); + while (enum.hasMoreElements()) { + Object elem = enum.nextElement(); + if (!result.contains(elem)) + result.addElement(elem); + } + return result; + } + + void promoteReads(InstrInfo info, Instruction preInstr, + BitSet mergeSet, boolean inverted) { + InstrInfo preInfo = (InstrInfo) instrInfos.get(preInstr); + int omitLocal = -1; + if (preInstr.getOpcode() >= opc_istore + && preInstr.getOpcode() <= opc_astore) { + /* This is a store */ + omitLocal = preInstr.getLocalSlot(); + if (info.nextReads[omitLocal] != null) + preInfo.local.combineInto(info.nextReads[omitLocal].local); + } + for (int i=0; i < maxlocals; i++) { + if (info.nextReads[i] != null && i != omitLocal + && (mergeSet == null || mergeSet.get(i) != inverted)) { + + if (preInfo.nextReads[i] == null) { + preInfo.nextReads[i] = info.nextReads[i]; + changedInfos.add(preInfo); + } else { + preInfo.nextReads[i].local + .combineInto(info.nextReads[i].local); + } + } + } + } + + void promoteReads(InstrInfo info, Instruction preInstr) { + promoteReads(info, preInstr, null, false); + } + + public LocalVariableInfo findLVTEntry(LocalVariableInfo[] lvt, + int slot, int addr) { + LocalVariableInfo match = null; + for (int i=0; i < lvt.length; i++) { + if (lvt[i].slot == slot + && lvt[i].start.getAddr() <= addr + && lvt[i].end.getAddr() >= addr) { + if (match != null + && (!match.name.equals(lvt[i].name) + || !match.type.equals(lvt[i].type))) { + /* Multiple matches..., give no info */ + return null; + } + match = lvt[i]; + } + } + return match; + } + + public LocalVariableInfo findLVTEntry(LocalVariableInfo[] lvt, + Instruction instr) { + int addr; + if (instr.getOpcode() >= opc_istore + && instr.getOpcode() <= opc_astore) + addr = instr.getNextAddr(); + else + addr = instr.getAddr(); + return findLVTEntry(lvt, instr.getLocalSlot(), addr); + } + + public void calcLocalInfo() { + maxlocals = bc.getMaxLocals(); + Handler[] handlers = bc.getExceptionHandlers(); + LocalVariableInfo[] lvt = bc.getLocalVariableTable(); + if (lvt != null) + produceLVT = true; + + /* Initialize paramLocals */ + { + String methodType = bc.getMethodInfo().getType(); + int paramCount = (bc.getMethodInfo().isStatic() ? 0 : 1) + + TypeSignature.getArgumentSize(methodType); + paramLocals = new LocalInfo[paramCount]; + int slot = 0; + if (!bc.getMethodInfo().isStatic()) { + LocalInfo local = new LocalInfo(); + if (lvt != null) { + LocalVariableInfo lvi = findLVTEntry(lvt, 0, 0); + if (lvi != null) { + local.name = lvi.name; + local.type = lvi.type; + } + } + local.size = 1; + paramLocals[slot++] = local; + } + int pos = 1; + while (pos < methodType.length() + && methodType.charAt(pos) != ')') { + + LocalInfo local = new LocalInfo(); + if (lvt != null) { + LocalVariableInfo lvi = findLVTEntry(lvt, slot, 0); + if (lvi != null) { + local.name = lvi.name; + } + } + + int start = pos; + pos = TypeSignature.skipType(methodType, pos); + local.type = methodType.substring(start, pos); + local.size = TypeSignature.getTypeSize(local.type); + paramLocals[slot] = local; + slot += local.size; + } + } + + /* Initialize the InstrInfos and LocalInfos + */ + changedInfos = new TodoQueue(); + instrInfos = new Hashtable(); + { + InstrInfo info = firstInfo = new InstrInfo(); + Iterator i = bc.getInstructions().iterator(); + while (true) { + Instruction instr = (Instruction) i.next(); + instrInfos.put(instr, info); + info.instr = instr; + info.nextReads = new InstrInfo[maxlocals]; + if (instr.hasLocalSlot()) { + info.local = new LocalInfo(info); + if (lvt != null) { + LocalVariableInfo lvi = findLVTEntry(lvt, instr); + if (lvi != null) { + info.local.name = lvi.name; + info.local.type = lvi.type; + } + } + info.local.size = 1; + switch (instr.getOpcode()) { + case opc_lload: case opc_dload: + info.local.size = 2; + /* fall through */ + case opc_iload: case opc_fload: case opc_aload: + case opc_iinc: + /* this is a load instruction */ + info.nextReads[instr.getLocalSlot()] = info; + changedInfos.add(info); + break; + + case opc_ret: + /* this is a ret instruction */ + info.usedBySub = new BitSet(); + info.nextReads[instr.getLocalSlot()] = info; + changedInfos.add(info); + break; + + case opc_lstore: case opc_dstore: + info.local.size = 2; + //case opc_istore: case opc_fstore: case opc_astore: + } + } + if (!i.hasNext()) + break; + info = info.nextInfo = new InstrInfo(); + } + } + + /* find out which locals are the same. + */ + while (!changedInfos.isEmpty()) { + InstrInfo info = changedInfos.remove(); + Instruction instr = info.instr; + + /* Mark the local as used in all ret instructions */ + if (instr.hasLocalSlot()) { + int slot = instr.getLocalSlot(); + for (int i=0; i< maxlocals; i++) { + InstrInfo retInfo = info.nextReads[i]; + if (retInfo != null + && retInfo.instr.getOpcode() == opc_ret + && !retInfo.usedBySub.get(slot)) { + retInfo.usedBySub.set(slot); + if (retInfo.jsrTargetInfo != null) + changedInfos.add(retInfo.jsrTargetInfo); + } + } + } + + Instruction prevInstr = instr.getPrevByAddr(); + if (prevInstr != null) { + if (!prevInstr.doesAlwaysJump()) + promoteReads(info, prevInstr); + else if (prevInstr.getOpcode() == opc_jsr) { + /* Prev instr is a jsr, promote reads to the + * corresponding ret. + */ + InstrInfo jsrInfo = + (InstrInfo) instrInfos.get(prevInstr.getSingleSucc()); + if (jsrInfo.retInfo != null) { + /* Now promote reads that are modified by the + * subroutine to the ret, and those that are not + * to the jsr instruction. + */ + promoteReads(info, jsrInfo.retInfo.instr, + jsrInfo.retInfo.usedBySub, false); + promoteReads(info, prevInstr, + jsrInfo.retInfo.usedBySub, true); + } + } + } + + if (instr.getPreds() != null) { + for (int i = 0; i < instr.getPreds().length; i++) { + Instruction predInstr = instr.getPreds()[i]; + if (instr.getPreds()[i].getOpcode() == opc_jsr) { + /* This is the target of a jsr instr. + */ + if (info.instr.getOpcode() != opc_astore) { + /* XXX Grrr, the bytecode verifier doesn't + * test if a jsr starts with astore. So + * it is possible to do something else + * before putting the ret address into a + * local. */ + throw new AssertError("Non standard jsr"); + } + InstrInfo retInfo = info.nextInfo.nextReads + [info.instr.getLocalSlot()]; + + if (retInfo != null) { + if (retInfo.instr.getOpcode() != opc_ret) + throw new AssertError + ("reading return address"); + + info.retInfo = retInfo; + retInfo.jsrTargetInfo = info; + + /* Now promote reads from the instruction + * after the jsr to the ret instruction if + * they are modified by the subroutine, + * and to the jsr instruction otherwise. + */ + Instruction nextInstr = predInstr.getNextByAddr(); + InstrInfo nextInfo + = (InstrInfo) instrInfos.get(nextInstr); + + promoteReads(nextInfo, retInfo.instr, + retInfo.usedBySub, false); + + promoteReads(nextInfo, predInstr, + retInfo.usedBySub, true); + } + } + promoteReads(info, instr.getPreds()[i]); + } + } + + for (int i=0; i < handlers.length; i++) { + if (handlers[i].catcher == instr) { + for (Instruction preInstr = handlers[i].start; + preInstr != handlers[i].end.getNextByAddr(); + preInstr = preInstr.getNextByAddr()) { + promoteReads(info, preInstr); + } + } + } + } + changedInfos = null; + + /* Now merge with the parameters + * The params should be the locals in firstInfo.nextReads + */ + for (int i=0; i< paramLocals.length; i++) { + if (firstInfo.nextReads[i] != null) { + firstInfo.nextReads[i].local.combineInto(paramLocals[i]); + paramLocals[i] = paramLocals[i].getReal(); + } + } + } + + public void stripLocals() { + ListIterator iter = bc.getInstructions().listIterator(); + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + Instruction instr = (Instruction) iter.next(); + if (info.local != null && info.local.usingInstrs.size() == 1) { + /* If this is a store, whose value is never read; it can + * be removed, i.e replaced by a pop. */ + switch (instr.getOpcode()) { + case opc_istore: + case opc_fstore: + case opc_astore: + iter.set(new Instruction(opc_pop)); + break; + case opc_lstore: + case opc_dstore: + iter.set(new Instruction(opc_pop2)); + break; + default: + } + } + } + } + + void distributeLocals(Vector locals) { + if (locals.size() == 0) + return; + + /* Find the local with the least conflicts. */ + int min = Integer.MAX_VALUE; + LocalInfo bestLocal = null; + Enumeration enum = locals.elements(); + while (enum.hasMoreElements()) { + LocalInfo li = (LocalInfo) enum.nextElement(); + int conflicts = 0; + Enumeration conflenum = li.conflictingLocals.elements(); + while (conflenum.hasMoreElements()) { + if (((LocalInfo)conflenum.nextElement()).newSlot != -2) + conflicts++; + } + if (conflicts < min) { + min = conflicts; + bestLocal = li; + } + } + /* Mark the local as taken */ + locals.removeElement(bestLocal); + bestLocal.newSlot = -2; + /* Now distribute the remaining locals recursively. */ + distributeLocals(locals); + + /* Finally find a new slot */ + next_slot: + for (int slot = 0; ; slot++) { + Enumeration conflenum = bestLocal.conflictingLocals.elements(); + while (conflenum.hasMoreElements()) { + LocalInfo conflLocal = (LocalInfo)conflenum.nextElement(); + if (bestLocal.size == 2 && conflLocal.newSlot == slot+1) { + slot++; + continue next_slot; + } + if (conflLocal.size == 2 && conflLocal.newSlot+1 == slot) + continue next_slot; + if (conflLocal.newSlot == slot) { + if (conflLocal.size == 2) + slot++; + continue next_slot; + } + } + bestLocal.newSlot = slot; + break; + } + } + + public void distributeLocals() { + /* give locals new slots. This is a graph coloring + * algorithm (the optimal solution is NP complete, but this + * should be a good approximation). + */ + + /* first give the params the same slot as they had before. + */ + for (int i=0; i= BytecodeInfo.opc_istore + && info.instr.getOpcode() <= BytecodeInfo.opc_astore) { + /* This is a store. It conflicts with every local, whose + * value will be read without write. + * + * If this is inside a ret, it also conflicts with + * locals, that are not used inside, and where any jsr + * would conflict with. + */ + for (int i=0; i < maxlocals; i++) { + if (i != info.instr.getLocalSlot() + && info.nextReads[i] != null) + info.local.conflictsWith(info.nextReads[i].local); + if (info.nextInfo.nextReads[i] != null + && info.nextInfo.nextReads[i].jsrTargetInfo != null) { + Instruction[] jsrs = info.nextInfo.nextReads[i] + .jsrTargetInfo.instr.getPreds(); + for (int j=0; j< jsrs.length; j++) { + InstrInfo jsrInfo + = (InstrInfo) instrInfos.get(jsrs[j]); + for (int k=0; k < maxlocals; k++) { + if (!info.nextInfo.nextReads[i].usedBySub + .get(k) + && jsrInfo.nextReads[k] != null) + info.local.conflictsWith + (jsrInfo.nextReads[k].local); + } + } + } + } + } + } + + /* Now put the locals that need a color into a vector. + */ + Vector locals = new Vector(); + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + if (info.local != null + && info.local.newSlot == -1 + && !locals.contains(info.local)) + locals.addElement(info.local); + } + + /* Now distribute slots recursive. + */ + distributeLocals(locals); + + /* Update the instructions and calculate new maxlocals. + */ + maxlocals = paramLocals.length; + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + if (info.local != null) { + if (info.local.newSlot+info.local.size > maxlocals) + maxlocals = info.local.newSlot + info.local.size; + info.instr.setLocalSlot(info.local.newSlot); + } + } + bc.setMaxLocals(maxlocals); + + /* Update LocalVariableTable + */ + if (produceLVT) + buildNewLVT(); + } + + private InstrInfo CONFLICT = new InstrInfo(); + + boolean promoteLifeLocals(LocalInfo[] newLife, InstrInfo nextInfo) { + if (nextInfo.lifeLocals == null) { + nextInfo.lifeLocals = (LocalInfo[]) newLife.clone(); + return true; + } + boolean changed = false; + for (int i=0; i< maxlocals; i++) { + LocalInfo local = nextInfo.lifeLocals[i]; + if (local == null) + /* A conflict has already happened, or this slot + * may not have been initialized. */ + continue; + + local = local.getReal(); + LocalInfo newLocal = newLife[i]; + if (newLocal != null) + newLocal = newLocal.getReal(); + if (local != newLocal) { + nextInfo.lifeLocals[i] = null; + changed = true; + } + } + return changed; + } + + public void buildNewLVT() { + /* First we recalculate the usedBySub, to use the new local numbers. + */ + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) + if (info.usedBySub != null) + info.usedBySub = new BitSet(); + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + if (info.local != null) { + for (int i=0; i < info.nextReads.length; i++) { + if (info.nextReads[i] != null + && info.nextReads[i].instr.getOpcode() == opc_ret) + info.nextReads[i].usedBySub.set(info.local.newSlot); + } + } + } + + /* Now we begin with the first Instruction and follow program flow. + * We remember which locals are life in lifeLocals. + */ + + firstInfo.lifeLocals = new LocalInfo[maxlocals]; + for (int i=0; i < paramLocals.length; i++) + firstInfo.lifeLocals[i] = paramLocals[i]; + + Stack changedInfo = new Stack(); + changedInfo.push(firstInfo); + Handler[] handlers = bc.getExceptionHandlers(); + while (!changedInfo.isEmpty()) { + InstrInfo info = (InstrInfo) changedInfo.pop(); + Instruction instr = info.instr; + LocalInfo[] newLife = info.lifeLocals; + if (instr.hasLocalSlot()) { + int slot = instr.getLocalSlot(); + LocalInfo instrLocal = info.local.getReal(); + newLife = (LocalInfo[]) newLife.clone(); + newLife[slot] = instrLocal; + if (instrLocal.name != null) { + for (int j=0; j< newLife.length; j++) { + if (j != slot + && newLife[j] != null + && instrLocal.name.equals(newLife[j].name)) { + /* This local changed the slot. */ + newLife[j] = null; + } + } + } + } + + if (!instr.doesAlwaysJump()) { + InstrInfo nextInfo = info.nextInfo; + if (promoteLifeLocals(newLife, nextInfo)) + changedInfo.push(nextInfo); + } + if (instr.hasSuccs()) { + Instruction[] succs = instr.getSuccs(); + for (int i = 0; i < succs.length; i++) { + InstrInfo nextInfo + = (InstrInfo) instrInfos.get(succs[i]); + if (promoteLifeLocals(newLife, nextInfo)) + changedInfo.push(nextInfo); + } + } + for (int i=0; i < handlers.length; i++) { + if (handlers[i].start.compareTo(instr) <= 0 + && handlers[i].end.compareTo(instr) >= 0) { + InstrInfo nextInfo + = (InstrInfo) instrInfos.get(handlers[i].catcher); + if (promoteLifeLocals(newLife, nextInfo)) + changedInfo.push(nextInfo); + } + } + + if (info.instr.getOpcode() == opc_jsr) { + /* On a jsr we do a special merge */ + + Instruction jsrTargetInstr = info.instr.getSingleSucc(); + InstrInfo jsrTargetInfo + = (InstrInfo) instrInfos.get(jsrTargetInstr); + InstrInfo retInfo = jsrTargetInfo.retInfo; + if (retInfo != null && retInfo.lifeLocals != null) { + LocalInfo[] retLife = (LocalInfo[]) newLife.clone(); + for (int i=0; i< maxlocals; i++) { + if (retInfo.usedBySub.get(i)) + retLife[i] = retInfo.lifeLocals[i]; + } + if (promoteLifeLocals(retLife, info.nextInfo)) + changedInfo.push(info.nextInfo); + } + } + + if (info.jsrTargetInfo != null) { + /* On a ret we do a special merge */ + + Instruction jsrTargetInstr = info.jsrTargetInfo.instr; + for (int j=0; j< jsrTargetInstr.getPreds().length; j++) { + InstrInfo jsrInfo + = (InstrInfo) instrInfos.get(jsrTargetInstr.getPreds()[j]); + + if (jsrInfo.lifeLocals == null) + /* life locals are not calculated, yet */ + continue; + LocalInfo[] retLife = (LocalInfo[]) newLife.clone(); + for (int i=0; i< maxlocals; i++) { + if (!info.usedBySub.get(i)) + retLife[i] = jsrInfo.lifeLocals[i]; + } + if (promoteLifeLocals(retLife, jsrInfo.nextInfo)) + changedInfo.push(jsrInfo.nextInfo); + } + } + } + + Vector lvtEntries = new Vector(); + LocalVariableInfo[] lvi = new LocalVariableInfo[maxlocals]; + LocalInfo[] currentLocal = new LocalInfo[maxlocals]; + for (int i=0; i< paramLocals.length; i++) { + if (paramLocals[i] != null) { + currentLocal[i] = paramLocals[i]; + if (currentLocal[i].name != null) { + lvi[i] = new LocalVariableInfo(); + lvtEntries.addElement(lvi[i]); + lvi[i].name = currentLocal[i].name; /* XXX obfuscation? */ + lvi[i].type = Main.getClassBundle() + .getTypeAlias(currentLocal[i].type); + lvi[i].start = (Instruction) bc.getInstructions().get(0); + lvi[i].slot = i; + } + } + } + Instruction lastInstr = null; + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + for (int i=0; i< maxlocals; i++) { + LocalInfo lcl = info.lifeLocals != null ? info.lifeLocals[i] + : null; + if (lcl != currentLocal[i] + && (lcl == null || currentLocal[i] == null + || lcl.name == null || lcl.type == null + || !lcl.name.equals(currentLocal[i].name) + || !lcl.type.equals(currentLocal[i].type))) { + if (lvi[i] != null) { + lvi[i].end = info.instr.getPrevByAddr(); + } + lvi[i] = null; + currentLocal[i] = lcl; + if (currentLocal[i] != null + && currentLocal[i].name != null + && currentLocal[i].type != null) { + lvi[i] = new LocalVariableInfo(); + lvtEntries.addElement(lvi[i]); + lvi[i].name = currentLocal[i].name; + lvi[i].type = Main.getClassBundle() + .getTypeAlias(currentLocal[i].type); + lvi[i].start = info.instr; + lvi[i].slot = i; + } + } + } + lastInstr = info.instr; + } + for (int i=0; i< maxlocals; i++) { + if (lvi[i] != null) + lvi[i].end = lastInstr; + } + LocalVariableInfo[] lvt = new LocalVariableInfo[lvtEntries.size()]; + lvtEntries.copyInto(lvt); + bc.setLocalVariableTable(lvt); + } + + public void dumpLocals() { + Vector locals = new Vector(); + for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) { + GlobalOptions.err.println(info.instr.getDescription()); + GlobalOptions.err.print("nextReads: "); + for (int i=0; i 0) + i.next(); + return (String) i.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public final Collection getCollection(Identifier ident) { + if (ident instanceof PackageIdentifier) + return packs; + else if (ident instanceof ClassIdentifier) + return clazzes; + else if (ident instanceof MethodIdentifier) + return methods; + else if (ident instanceof FieldIdentifier) + return fields; + else if (ident instanceof LocalIdentifier) + return locals; + else + throw new IllegalArgumentException(ident.getClass().getName()); + } + + public final void addIdentifierName(Identifier ident) { + getCollection(ident).add(ident.getName()); + } + + public Iterator generateNames(Identifier ident) { + return new NameGenerator(getCollection(ident)); + } +} + + diff --git a/jode/jode/obfuscator/modules/RemovePopAnalyzer.java.in b/jode/jode/obfuscator/modules/RemovePopAnalyzer.java.in new file mode 100644 index 0000000..2609082 --- /dev/null +++ b/jode/jode/obfuscator/modules/RemovePopAnalyzer.java.in @@ -0,0 +1,308 @@ +/* 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 jode.obfuscator.modules; +import jode.bytecode.*; +import jode.obfuscator.*; +import jode.AssertError; +import jode.GlobalOptions; + +import @COLLECTIONS@.ListIterator; + +public class RemovePopAnalyzer implements CodeTransformer, Opcodes { + public RemovePopAnalyzer() { + } + + public void transformCode(BytecodeInfo bytecode) { + int poppush[] = new int[2]; + ListIterator iter = bytecode.getInstructions().listIterator(); + next_pop: + while (iter.hasNext()) { + Instruction popInstr = (Instruction) iter.next(); + boolean isPop2 = false; + switch (popInstr.getOpcode()) { + case opc_nop: { + iter.remove(); + continue; + } + + case opc_pop2: + isPop2 = true; + case opc_pop: + if (popInstr.getPreds() != null) + // Can't handle pop with multiple predecessors + continue next_pop; + Handler[] handlers = bytecode.getExceptionHandlers(); + for (int i=0; i < handlers.length; i++) + if (handlers[i].catcher == popInstr) + continue next_pop; + + // remove pop, we will insert it again if something + // bad happened. + iter.remove(); + + // remember position of pop, so we can insert it again. + Instruction popPrevious = (Instruction) iter.previous(); + Instruction instr = popPrevious; + int count = 0; + while (true) { + if (instr.getSuccs() != null + || instr.doesAlwaysJump()) { + instr = null; + break; + } + instr.getStackPopPush(poppush); + + if (count < poppush[1]) { + if (count == 0) + break; + + int opcode = instr.getOpcode(); + /* If this is a dup and the instruction popped is the + * duplicated element, remove the dup and the pop + */ + if (count <= 3 && opcode == (opc_dup + count - 1)) { + iter.remove(); + if (!isPop2) + continue next_pop; + + // We have to consider a pop instead of a + // pop2 now. + popInstr = new Instruction(opc_pop); + isPop2 = false; + instr = (Instruction) iter.previous(); + continue; + } + + if (isPop2 + && count > 1 && count <= 4 + && opcode == (opc_dup2 + count-2)) { + iter.remove(); + continue next_pop; + } + /* Otherwise popping is not possible */ + instr = null; + break; + } + count += poppush[0] - poppush[1]; + instr = (Instruction) iter.previous(); + } + + if (instr == null) { + // We insert the pop at the previous position + while (iter.next() != popPrevious) + {} + if (!isPop2 && popPrevious.getOpcode() == opc_pop) { + // merge pop with popPrevious + iter.set(new Instruction(opc_pop2)); + } else + iter.add(popInstr); + continue; + } + int opcode = instr.getOpcode(); + switch (opcode) { + case opc_ldc2_w: + case opc_lload: case opc_dload: + if (!isPop2) + throw new AssertError("pop on long"); + iter.remove(); + continue; + case opc_ldc: + case opc_iload: case opc_fload: case opc_aload: + case opc_dup: + case opc_new: + if (isPop2) + iter.set(new Instruction(opc_pop)); + else + iter.remove(); + continue; + 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: + /* We have to pop one entry more. */ + iter.next(); + iter.add(popInstr); + iter.previous(); + iter.previous(); + iter.set(new Instruction(opc_pop)); + continue; + + case opc_dup_x1: + iter.set(new Instruction(opc_swap)); + iter.next(); + if (isPop2) + iter.add(new Instruction(opc_pop)); + continue; + + case opc_dup2: + if (isPop2) { + iter.remove(); + continue; + } + break; + case opc_swap: + if (isPop2) { + iter.set(popInstr); + continue; + } + break; + + case opc_lneg: case opc_dneg: + case opc_l2d: case opc_d2l: + case opc_laload: case opc_daload: + if (!isPop2) + throw new AssertError("pop on long"); + /* fall through */ + 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: + iter.set(popInstr); + continue; + + case opc_l2i: case opc_l2f: + case opc_d2i: case opc_d2f: + if (isPop2) { + iter.next(); + iter.add(new Instruction(opc_pop)); + iter.previous(); + iter.previous(); + } + iter.set(new Instruction(opc_pop2)); + continue; + + 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: + if (!isPop2) + throw new AssertError("pop on long"); + iter.next(); + iter.add(popInstr); + iter.previous(); + iter.previous(); + iter.set(new Instruction(opc_pop2)); + continue; + case opc_lshl: case opc_lshr: case opc_lushr: + if (!isPop2) + throw new AssertError("pop on long"); + iter.next(); + iter.add(popInstr); + iter.previous(); + iter.previous(); + iter.set(new Instruction(opc_pop)); + continue; + + case opc_i2l: case opc_i2d: + case opc_f2l: case opc_f2d: + if (!isPop2) + throw new AssertError("pop on long"); + iter.set(new Instruction(opc_pop)); + continue; + + case opc_lcmp: + case opc_dcmpl: case opc_dcmpg: + iter.next(); + iter.add(new Instruction(opc_pop2)); + if (isPop2) { + iter.add(new Instruction(opc_pop)); + iter.previous(); + } + iter.previous(); + iter.previous(); + iter.set(new Instruction(opc_pop2)); + continue; + + case opc_getstatic: + case opc_getfield: { + Reference ref = instr.getReference(); + int size = TypeSignature.getTypeSize(ref.getType()); + if (size == 2 && !isPop2) + throw new AssertError("pop on long"); + if (opcode == opc_getfield) + size--; + switch (size) { + case 0: + iter.set(popInstr); + break; + case 1: + if (isPop2) { + iter.set(new Instruction(opc_pop)); + break; + } + /* fall through */ + case 2: + iter.remove(); + } + continue; + } + + case opc_multianewarray: { + int dims = instr.getDimensions(); + if (--dims > 0) { + iter.next(); + while (dims-- > 0) { + iter.add(new Instruction(opc_pop)); + iter.previous(); + } + iter.previous(); + } + iter.set(popInstr); + continue; + } + + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + if (TypeSignature.getReturnSize + (instr.getReference().getType()) != 1) + break; + /* fall through */ + case opc_checkcast: + if (isPop2) { + /* This is/may be a double pop on a single value + * split it and continue with second half + */ + iter.next(); + iter.add(new Instruction(opc_pop)); + iter.add(new Instruction(opc_pop)); + iter.previous(); + continue; + } + } + // append the pop behind the unresolvable opcode. + iter.next(); + iter.add(popInstr); + continue; + } + } + } +} diff --git a/jode/jode/obfuscator/modules/SimpleAnalyzer.java.in b/jode/jode/obfuscator/modules/SimpleAnalyzer.java.in new file mode 100644 index 0000000..ec5459c --- /dev/null +++ b/jode/jode/obfuscator/modules/SimpleAnalyzer.java.in @@ -0,0 +1,184 @@ +/* SimpleAnalyzer 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 jode.obfuscator.modules; +import jode.bytecode.Handler; +import jode.bytecode.Opcodes; +import jode.bytecode.ClassInfo; +import jode.bytecode.BytecodeInfo; +import jode.bytecode.Instruction; +import jode.bytecode.Reference; +import jode.bytecode.TypeSignature; +import jode.obfuscator.Identifier; +import jode.obfuscator.*; +import jode.GlobalOptions; + +import @COLLECTIONS@.Iterator; +import @COLLECTIONS@.ListIterator; + +public class SimpleAnalyzer implements CodeAnalyzer, Opcodes { + + public Identifier canonizeReference(Instruction instr) { + Reference ref = instr.getReference(); + Identifier ident = Main.getClassBundle().getIdentifier(ref); + String clName = ref.getClazz(); + String realClazzName; + if (ident != null) { + ClassIdentifier clazz = (ClassIdentifier)ident.getParent(); + realClazzName = "L" + (clazz.getFullName() + .replace('.', '/')) + ";"; + } else { + /* We have to look at the ClassInfo's instead, to + * point to the right method. + */ + ClassInfo clazz; + if (clName.charAt(0) == '[') { + /* Arrays don't define new methods (well clone(), + * but that can be ignored). + */ + clazz = ClassInfo.javaLangObject; + } else { + clazz = ClassInfo.forName + (clName.substring(1, clName.length()-1) + .replace('/','.')); + } + if (instr.getOpcode() >= opc_invokevirtual) { + while (clazz != null + && clazz.findMethod(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } else { + while (clazz != null + && clazz.findField(ref.getName(), + ref.getType()) == null) + clazz = clazz.getSuperclass(); + } + + if (clazz == null) { + GlobalOptions.err.println("WARNING: Can't find reference: " + +ref); + realClazzName = clName; + } else + realClazzName = "L" + clazz.getName().replace('.', '/') + ";"; + } + if (!realClazzName.equals(ref.getClazz())) { + ref = Reference.getReference(realClazzName, + ref.getName(), ref.getType()); + instr.setReference(ref); + } + return ident; + } + + + /** + * Reads the opcodes out of the code info and determine its + * references + * @return an enumeration of the references. + */ + public void analyzeCode(MethodIdentifier m, BytecodeInfo bytecode) { + for (Iterator iter = bytecode.getInstructions().iterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + switch (instr.getOpcode()) { + case opc_checkcast: + case opc_instanceof: + case opc_multianewarray: { + String clName = instr.getClazzType(); + int i = 0; + while (i < clName.length() && clName.charAt(i) == '[') + i++; + if (i < clName.length() && clName.charAt(i) == 'L') { + clName = clName.substring(i+1, clName.length()-1) + .replace('/','.'); + Main.getClassBundle().reachableClass(clName); + } + break; + } + case opc_invokespecial: + case opc_invokestatic: + case opc_invokeinterface: + case opc_invokevirtual: + case opc_putstatic: + case opc_putfield: + m.setGlobalSideEffects(); + /* fall through */ + case opc_getstatic: + case opc_getfield: { + Identifier ident = canonizeReference(instr); + if (ident != null) { + if (instr.getOpcode() == opc_putstatic + || instr.getOpcode() == opc_putfield) { + FieldIdentifier fi = (FieldIdentifier) ident; + if (fi != null && !fi.isNotConstant()) + fi.setNotConstant(); + } else if (instr.getOpcode() == opc_invokevirtual + || instr.getOpcode() == opc_invokeinterface) { + ((ClassIdentifier) ident.getParent()) + .reachableReference(instr.getReference(), true); + } else { + ident.setReachable(); + } + } + break; + } + } + } + + Handler[] handlers = bytecode.getExceptionHandlers(); + for (int i=0; i< handlers.length; i++) { + if (handlers[i].type != null) + Main.getClassBundle() + .reachableClass(handlers[i].type); + } + } + + public void transformCode(BytecodeInfo bytecode) { + for (ListIterator iter = bytecode.getInstructions().listIterator(); + iter.hasNext(); ) { + Instruction instr = (Instruction) iter.next(); + if (instr.getOpcode() == opc_putstatic + || instr.getOpcode() == opc_putfield) { + Reference ref = instr.getReference(); + FieldIdentifier fi = (FieldIdentifier) + Main.getClassBundle().getIdentifier(ref); + if (fi != null + && (Main.stripping & Main.STRIP_UNREACH) != 0 + && !fi.isReachable()) { + /* Replace instruction with pop opcodes. */ + int stacksize = + (instr.getOpcode() + == Instruction.opc_putstatic) ? 0 : 1; + stacksize += TypeSignature.getTypeSize(ref.getType()); + switch (stacksize) { + case 1: + iter.set(new Instruction(Instruction.opc_pop)); + break; + case 2: + iter.set(new Instruction(Instruction.opc_pop2)); + break; + case 3: + iter.set(new Instruction(Instruction.opc_pop2)); + iter.add(new Instruction(Instruction.opc_pop)); + break; + } + } + } + } + } +} diff --git a/jode/jode/obfuscator/modules/StrongRenamer.java.in b/jode/jode/obfuscator/modules/StrongRenamer.java.in new file mode 100644 index 0000000..aa77110 --- /dev/null +++ b/jode/jode/obfuscator/modules/StrongRenamer.java.in @@ -0,0 +1,142 @@ +/* StrongRenamer 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 jode.obfuscator.modules; +import jode.obfuscator.*; +import @COLLECTIONS@.Collection; +import @COLLECTIONS@.Iterator; +import @COLLECTIONEXTRA@.UnsupportedOperationException; + +public class StrongRenamer implements Renamer, OptionHandler { + static String[] idents = { + "Package", "Class", "Field", "Method", "Local" + }; + static String[] parts = { + "Start", "Part" + }; + String charsets[][]; + + public StrongRenamer() { + charsets = new String[idents.length][parts.length]; + for (int i=0; i< idents.length; i++) + for (int j=0; j< parts.length; j++) + charsets[i][j] = "abcdefghijklmnopqrstuvwxyz"; + } + + public void setOption(String option, Collection values) { + if (option.startsWith("charset")) { + Object value = values.iterator().next(); + if (values.size() != 1 || !(value instanceof String)) + throw new IllegalArgumentException + ("Only string parameter are supported."); + String set = (String) value; + String remOpt = option.substring("charset".length()); + int part = -1, ident = -1; + if (remOpt.length() > 0) { + for (int i=0; i < idents.length; i++) { + if (remOpt.startsWith(idents[i])) { + remOpt = remOpt.substring(idents[i].length()); + ident = i; + break; + } + } + } + if (remOpt.length() > 0) { + for (int j=0; j < parts.length; j++) { + if (remOpt.startsWith(parts[j])) { + remOpt = remOpt.substring(parts[j].length()); + part = j; + break; + } + } + } + if (remOpt.length() > 0) + throw new IllegalArgumentException("Invalid charset `" + +option+"'"); + for (int i = 0; i < idents.length; i++) { + if (ident >= 0 && ident != i) + continue; + for (int j = 0; j < parts.length; j++) { + if (part >= 0 && part != j) + continue; + charsets[i][j] = set; + } + } + } else + throw new IllegalArgumentException("Invalid option `" + +option+"'"); + } + + public Iterator generateNames(Identifier ident) { + final String[] currCharset; + if (ident instanceof PackageIdentifier) + currCharset = charsets[0]; + else if (ident instanceof PackageIdentifier) + currCharset = charsets[1]; + else if (ident instanceof ClassIdentifier) + currCharset = charsets[2]; + else if (ident instanceof FieldIdentifier) + currCharset = charsets[3]; + else if (ident instanceof MethodIdentifier) + currCharset = charsets[4]; + else if (ident instanceof LocalIdentifier) + currCharset = charsets[5]; + else + throw new IllegalArgumentException(ident.getClass().getName()); + + return new Iterator() { + char[] name = null; + + public boolean hasNext() { + return true; + } + public Object next() { + if (name == null) { + name = new char[] { currCharset[0].charAt(0) }; + return new String(name); + } + + int pos = name.length - 1; + String charset = currCharset[1]; + while (pos >= 0) { + if (pos == 0) + charset = currCharset[0]; + + int index = charset.indexOf(name[pos]) + 1; + if (index < charset.length()) { + name[pos] = charset.charAt(index); + return new String(name); + } + name[pos--] = charset.charAt(0); + } + + name = new char[name.length+1]; + name[0] = currCharset[0].charAt(0); + char firstCont = currCharset[1].charAt(0); + for (int i=1; i 0) + prefix += "."; + + int lastDot = prefix.length(); + if (!wildcard.startsWith(prefix)) + return null; + + int nextDot = wildcard.indexOf('.', lastDot); + if (nextDot > 0 + && (nextDot <= firstStar || firstStar == -1)) + return wildcard.substring(lastDot, nextDot); + else if (firstStar == -1) + return wildcard.substring(lastDot); + else + return null; + } + + public boolean matchesSub(Identifier ident, String subident) { + String prefix = ident.getFullName(); + if (prefix.length() > 0) + prefix += "."; + if (subident != null) + prefix += subident; + if (firstStar == -1 || firstStar >= prefix.length()) + return wildcard.startsWith(prefix); + return prefix.startsWith(wildcard.substring(0, firstStar)); + } + + public boolean matches(Identifier ident) { + String test = ident.getFullName(); + if (firstStar == -1) { + if (wildcard.equals(test)) { + return true; + } + return false; + } + if (!test.startsWith(wildcard.substring(0, firstStar))) + return false; + + test = test.substring(firstStar); + int lastWild = firstStar; + int nextWild; + while ((nextWild = wildcard.indexOf('*', lastWild + 1)) != -1) { + String pattern = wildcard.substring(lastWild+1, nextWild); + while (!test.startsWith(pattern)) { + if (test.length() == 0) + return false; + test = test.substring(1); + } + test = test.substring(nextWild - lastWild - 1); + lastWild = nextWild; + } + + return test.endsWith(wildcard.substring(lastWild+1)); + } + + public String toString() { + return "Wildcard "+wildcard; + } +}