move modules into separate package

serializable reworked (may be buggy still)


git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@1173 379699f6-c40d-0410-875b-85095c16579e
branch_1_1
jochen 25 years ago
parent 8f32e39421
commit f8384e9928
  1. 3
      jode/jode/obfuscator/ClassBundle.java.in
  2. 72
      jode/jode/obfuscator/ClassIdentifier.java.in
  3. 1706
      jode/jode/obfuscator/ConstantAnalyzer.java.in
  4. 9
      jode/jode/obfuscator/FieldIdentifier.java.in
  5. 6
      jode/jode/obfuscator/Identifier.java.in
  6. 940
      jode/jode/obfuscator/LocalOptimizer.java.in
  7. 34
      jode/jode/obfuscator/LocalizeFieldTransformer.java.in
  8. 15
      jode/jode/obfuscator/Makefile.am
  9. 4
      jode/jode/obfuscator/MethodIdentifier.java.in
  10. 269
      jode/jode/obfuscator/ModifierMatcher.java
  11. 109
      jode/jode/obfuscator/MultiIdentifierMatcher.java.in
  12. 98
      jode/jode/obfuscator/NameSwapper.java.in
  13. 7
      jode/jode/obfuscator/PackageIdentifier.java.in
  14. 307
      jode/jode/obfuscator/RemovePopAnalyzer.java.in
  15. 3
      jode/jode/obfuscator/ScriptParser.java.in
  16. 182
      jode/jode/obfuscator/SimpleAnalyzer.java.in
  17. 141
      jode/jode/obfuscator/StrongRenamer.java.in
  18. 41
      jode/jode/obfuscator/UniqueRenamer.java.in
  19. 108
      jode/jode/obfuscator/WildCard.java.in

@ -22,6 +22,9 @@ import jode.GlobalOptions;
import jode.bytecode.SearchPath;
import jode.bytecode.ClassInfo;
import jode.bytecode.Reference;
import jode.obfuscator.modules.WildCard;
import jode.obfuscator.modules.MultiIdentifierMatcher;
import jode.obfuscator.modules.SimpleAnalyzer;
import java.io.*;
import java.util.zip.ZipOutputStream;

@ -20,6 +20,7 @@
package jode.obfuscator;
import jode.GlobalOptions;
import jode.bytecode.*;
import jode.obfuscator.modules.ModifierMatcher;
import @COLLECTIONS@.Comparator;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.Collections;
@ -251,48 +252,26 @@ public class ClassIdentifier extends Identifier {
}
}
/**
* Preserve all fields, that are necessary, to serialize
* a compatible class.
*/
public void preserveSerializable() {
Identifier method
= findMethod("writeObject", "(Ljava.io.ObjectOutputStream)V");
if (method != null)
method.setPreserved();
method = findMethod("readObject", "(Ljava.io.ObjectInputStream)V");
if (method != null)
method.setPreserved();
if ((Main.options & Main.OPTION_PRESERVESERIAL) != 0) {
setPreserved();
Identifier UIDident = findField("serialVersionUID", "J");
if (UIDident == null) {
/* add a field serializableVersionUID if not existent */
long serialVersion = calcSerialVersionUID();
FieldInfo UIDField = new FieldInfo
(info, "serialVersionUID", "J",
Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
UIDField.setConstant(new Long(serialVersion));
UIDident = new FieldIdentifier(this, UIDField);
fieldIdents.add(UIDident);
}
UIDident.setReachable();
UIDident.setPreserved();
for (Iterator i=getFieldIdents().iterator(); i.hasNext(); ) {
FieldIdentifier ident = (FieldIdentifier) i.next();
if ((ident.info.getModifiers()
& (Modifier.TRANSIENT | Modifier.STATIC)) == 0) {
ident.setPreserved();
ident.setNotConstant();
}
/* XXX - only preserve them if writeObject not existent
* or if writeObject calls defaultWriteObject, and similar
* for readObject
*/
}
}
public void addSUID() {
/* add a field serializableVersionUID if not existent */
long serialVersion = calcSerialVersionUID();
FieldInfo UIDField = new FieldInfo
(info, "serialVersionUID", "J",
Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
UIDField.setConstant(new Long(serialVersion));
FieldIdentifier UIDident = new FieldIdentifier(this, UIDField);
fieldIdents.add(UIDident);
UIDident.setPreserved();
}
public boolean isSerializable() {
return ClassInfo.forName("java.lang.Serializable")
.implementedBy(info);
}
public boolean hasSUID() {
return (findField("serialVersionUID", "J") != null);
}
/**
* Marks the package as preserved, too.
*/
@ -307,8 +286,6 @@ public class ClassIdentifier extends Identifier {
public void analyzeSuperClasses(ClassInfo superclass) {
while (superclass != null) {
if (superclass.getName().equals("java.io.Serializable"))
preserveSerializable();
ClassIdentifier superident = Main.getClassBundle()
.getClassIdentifier(superclass.getName());
@ -351,9 +328,6 @@ public class ClassIdentifier extends Identifier {
public void initSuperClasses(ClassInfo superclass) {
while (superclass != null) {
if (superclass.getName().equals("java.lang.Serializable"))
preserveSerializable();
ClassIdentifier superident = Main.getClassBundle()
.getClassIdentifier(superclass.getName());
if (superident != null) {
@ -736,6 +710,10 @@ public class ClassIdentifier extends Identifier {
public String getType() {
return "Ljava/lang/Class;";
}
public int getModifiers() {
return info.getModifiers();
}
public List getFieldIdents() {
return fieldIdents;
@ -796,7 +774,7 @@ public class ClassIdentifier extends Identifier {
}
public boolean containsFieldAliasDirectly(String fieldName, String typeSig,
ModifierMatcher matcher) {
IdentifierMatcher matcher) {
for (Iterator i = fieldIdents.iterator(); i.hasNext(); ) {
Identifier ident = (Identifier) i.next();
if (((Main.stripping & Main.STRIP_UNREACH) == 0
@ -812,7 +790,7 @@ public class ClassIdentifier extends Identifier {
public boolean containsMethodAliasDirectly(String methodName,
String paramType,
ModifierMatcher matcher) {
IdentifierMatcher matcher) {
for (Iterator i = methodIdents.iterator(); i.hasNext(); ) {
Identifier ident = (Identifier) i.next();
if (((Main.stripping & Main.STRIP_UNREACH) == 0

File diff suppressed because it is too large Load Diff

@ -60,6 +60,11 @@ public class FieldIdentifier extends Identifier{
Main.getClassBundle().analyzeIdentifier(this);
}
public void setSinglePreserved() {
super.setSinglePreserved();
setNotConstant();
}
public void analyze() {
String type = getType();
int index = type.indexOf('L');
@ -91,6 +96,10 @@ public class FieldIdentifier extends Identifier{
return type;
}
public int getModifiers() {
return info.getModifiers();
}
public Iterator getChilds() {
return Collections.EMPTY_LIST.iterator();
}

@ -226,7 +226,11 @@ public abstract class Identifier {
if (preserveRule.matches(this)) {
System.err.println("preserving: "+this);
setReachable();
setPreserved();
Identifier ident = this;
while (ident != null) {
ident.setPreserved();
ident = ident.getParent();
}
}
for (Iterator i = getChilds(); i.hasNext(); )
((Identifier)i.next()).applyPreserveRule(preserveRule);

@ -1,940 +0,0 @@
/* 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;
import java.util.*;
import jode.bytecode.*;
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<paramLocals.length; i++)
if (paramLocals[i] != null)
paramLocals[i].newSlot = i;
/* Now calculate the conflict settings.
*/
for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) {
if (info.instr.getOpcode() >= 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<maxlocals; i++)
if (info.nextReads[i] == null)
GlobalOptions.err.print("-,");
else
GlobalOptions.err.print(info.nextReads[i].instr.getAddr()+",");
if (info.usedBySub != null)
GlobalOptions.err.print(" usedBySub: "+info.usedBySub);
if (info.retInfo != null)
GlobalOptions.err.print(" ret info: "
+info.retInfo.instr.getAddr());
if (info.jsrTargetInfo != null)
GlobalOptions.err.print(" jsr info: "
+info.jsrTargetInfo.instr.getAddr());
GlobalOptions.err.println();
if (info.local != null && !locals.contains(info.local))
locals.addElement(info.local);
}
Enumeration enum = locals.elements();
while (enum.hasMoreElements()) {
LocalInfo li = (LocalInfo) enum.nextElement();
int slot = ((InstrInfo)li.usingInstrs.elementAt(0))
.instr.getLocalSlot();
GlobalOptions.err.print("Slot: "+slot+" conflicts:");
Enumeration enum1 = li.conflictingLocals.elements();
while (enum1.hasMoreElements()) {
LocalInfo cfl = (LocalInfo)enum1.nextElement();
GlobalOptions.err.print(cfl.getFirstAddr()+", ");
}
GlobalOptions.err.println();
GlobalOptions.err.print(li.getFirstAddr());
GlobalOptions.err.print(" instrs: ");
Enumeration enum2 = li.usingInstrs.elements();
while (enum2.hasMoreElements())
GlobalOptions.err.print(((InstrInfo)enum2.nextElement())
.instr.getAddr()+", ");
GlobalOptions.err.println();
}
GlobalOptions.err.println("-----------");
}
public void transformCode(BytecodeInfo bytecode) {
this.bc = bytecode;
calcLocalInfo();
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_LOCALS) != 0) {
GlobalOptions.err.println("Before Local Optimization: ");
dumpLocals();
}
stripLocals();
distributeLocals();
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_LOCALS) != 0) {
GlobalOptions.err.println("After Local Optimization: ");
dumpLocals();
}
firstInfo = null;
changedInfos = null;
instrInfos = null;
paramLocals = null;
}
}

@ -1,34 +0,0 @@
/* LocalizeFieldTransformer 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;
/**
* This class replaces accesses to local fields and .
*
*/
public class LocalizeFieldTransformer implements CodeTransformer {
public static void transformCode(BytecodeInfo bytecode) {
for (Instruction instr = bytecode.getFirstInstr();
instr != null; instr = instr.nextByAddr) {
}
}
}

@ -1,5 +1,7 @@
## Input file for automake to generate the Makefile.in used by configure
SUBDIRS = modules
JAR = @JAR@
JAVAC = @JAVAC@
JAVADEP = $(top_builddir)/javaDependencies.pl -subdir=$(subdir)\
@ -15,30 +17,19 @@ MY_JAVA_FILES = \
ClassIdentifier.java \
CodeAnalyzer.java \
CodeTransformer.java \
ConstantAnalyzer.java \
ConstantRuntimeEnvironment.java \
FieldIdentifier.java \
Identifier.java \
IdentifierMatcher.java \
LocalIdentifier.java \
LocalOptimizer.java \
Main.java \
MethodIdentifier.java \
ModifierMatcher.java \
MultiIdentifierMatcher.java \
NameSwapper.java \
OptionHandler.java \
PackageIdentifier.java \
ParseException.java \
RemovePopAnalyzer.java \
Renamer.java \
ScriptParser.java \
SimpleAnalyzer.java \
StrongRenamer.java \
TranslationTable.java \
UniqueRenamer.java \
WildCard.java
# LocalizeFieldTransformer.java
TranslationTable.java
noinst_DATA = $(MY_JAVA_FILES:.java=.class)
EXTRA_DIST = $(MY_JAVA_FILES)

@ -122,6 +122,10 @@ public class MethodIdentifier extends Identifier implements Opcodes {
return type;
}
public int getModifiers() {
return info.getModifiers();
}
public boolean conflicting(String newAlias) {
return clazz.methodConflicts(this, newAlias);
}

@ -1,269 +0,0 @@
/* ModifierMatcher 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;
import java.lang.reflect.Modifier;
public class ModifierMatcher implements IdentifierMatcher, Cloneable {
static final int PUBLIC = Modifier.PUBLIC;
static final int PROTECTED = Modifier.PROTECTED;
static final int PRIVATE = Modifier.PRIVATE;
int[] andMasks;
int[] xorMasks;
public static ModifierMatcher denyAll = new ModifierMatcher(new int[0],
new int[0]);
public static ModifierMatcher allowAll = new ModifierMatcher(0, 0);
/* Invariants:
* \forall i: ~andMasks[i] & xorMasks[i] == 0
* \forall i: entries wo. i does not imply entry nr. i
*/
private ModifierMatcher(int[] ands, int[] xors) {
andMasks = ands;
xorMasks = xors;
}
public ModifierMatcher(int and, int xor) {
andMasks = new int[] { and };
xorMasks = new int[] { xor };
}
private static boolean implies(int and1, int xor1, int and2, int xor2) {
return ((and1 & and2) == and2 && (xor1 & and2) == xor2);
}
private boolean implies(int and, int xor) {
for (int i=0; i < andMasks.length; i++) {
if (!implies(andMasks[i], xorMasks[i], and, xor))
return false;
}
return true;
}
private boolean impliedBy(int and, int xor) {
for (int i=0; i< andMasks.length; i++) {
if (implies(and, xor, andMasks[i], xorMasks[i]))
return true;
}
return false;
}
private boolean implies(ModifierMatcher mm) {
for (int i=0; i < andMasks.length; i++) {
if (!mm.impliedBy(andMasks[i], xorMasks[i]))
return false;
}
return true;
}
public ModifierMatcher and(ModifierMatcher mm) {
if (implies(mm))
return this;
if (mm.implies(this))
return mm;
ModifierMatcher result = denyAll;
for (int i=0; i< andMasks.length; i++)
result = result.or(mm.and(andMasks[i], xorMasks[i]));
return result;
}
public ModifierMatcher or(ModifierMatcher mm) {
if (implies(mm))
return mm;
if (mm.implies(this))
return this;
ModifierMatcher result = this;
for (int i=0; i < mm.andMasks.length; i++)
result = result.or(mm.andMasks[i], mm.xorMasks[i]);
return result;
}
private ModifierMatcher and(int and, int xor) {
if (this.implies(and, xor))
return this;
int newCount = 0;
next_i:
for (int i=0; i < andMasks.length; i++) {
if (implies(and, xor, andMasks[i], xorMasks[i]))
continue next_i;
for (int j=0; j < andMasks.length; j++) {
if (j != i
&& implies(and | andMasks[j], xor | xorMasks[j],
andMasks[i], xorMasks[i]))
continue next_i;
}
newCount++;
}
if (newCount == 0)
return new ModifierMatcher(and, xor);
int[] ands = new int[newCount];
int[] xors = new int[newCount];
int index = 0;
next_i:
for (int i=0; i < newCount; i++) {
if (implies(and, xor, andMasks[i], xorMasks[i]))
continue next_i;
for (int j=0; j < andMasks.length; j++) {
if (j != i
&& implies(and | andMasks[j], xor | xorMasks[j],
andMasks[i], xorMasks[i]))
continue next_i;
}
ands[index] = andMasks[i] | and;
xors[index] = xorMasks[i] | xor;
index++;
}
return new ModifierMatcher(ands, xors);
}
private ModifierMatcher or(int and, int xor) {
int matchIndex = -1;
if (this == denyAll)
return new ModifierMatcher(and, xor);
for (int i=0; i< andMasks.length; i++) {
if (implies(and, xor, andMasks[i], xorMasks[i]))
return this;
if (implies(andMasks[i], xorMasks[i], and, xor)) {
matchIndex = i;
break;
}
}
int[] ands, xors;
if (matchIndex == -1) {
matchIndex = andMasks.length;
ands = new int[matchIndex+1];
xors = new int[matchIndex+1];
System.arraycopy(andMasks, 0, ands, 0, matchIndex);
System.arraycopy(xorMasks, 0, xors, 0, matchIndex);
} else {
ands = (int[]) andMasks.clone();
xors = (int[]) xorMasks.clone();
}
ands[matchIndex] = and;
xors[matchIndex] = xor;
return new ModifierMatcher(ands, xors);
}
/**
* Creates a new ModifierMatcher, that matches only modifiers, we
* also match and also forces the access rights, to be accessModif
* (or less restrictive).
* @param accessModif the access modifier. Use 0 for package access,
* or Modifier.PRIVATE/PROTECTED/PUBLIC.
* @param andAbove allow to be less restrictive.
* @return a new modifier matcher that will also use the given accesses.
*/
public ModifierMatcher forceAccess(int accessModif, boolean andAbove) {
if (andAbove) {
if (accessModif == Modifier.PRIVATE)
return this;
if (accessModif == 0)
return this.and(Modifier.PRIVATE, 0);
ModifierMatcher result = this.and(Modifier.PUBLIC, PUBLIC);
if (accessModif == Modifier.PROTECTED)
return result
.or(this.and(Modifier.PROTECTED, Modifier.PROTECTED));
if (accessModif == Modifier.PUBLIC)
return result;
throw new IllegalArgumentException(""+accessModif);
} else {
if (accessModif == 0)
return this.and(Modifier.PRIVATE |
Modifier.PROTECTED | Modifier.PUBLIC, 0);
else
return this.and(accessModif, accessModif);
}
}
public ModifierMatcher forbidAccess(int accessModif, boolean andAbove) {
if (andAbove) {
if (accessModif == Modifier.PRIVATE)
// This forbids all access.
return denyAll;
if (accessModif == 0)
return this.and(Modifier.PRIVATE, Modifier.PRIVATE);
if (accessModif == Modifier.PROTECTED)
return this.and(Modifier.PROTECTED | Modifier.PUBLIC, 0);
if (accessModif == Modifier.PUBLIC)
return this.and(Modifier.PUBLIC, 0);
throw new IllegalArgumentException(""+accessModif);
} else {
if (accessModif == 0) {
return this.and(Modifier.PRIVATE, Modifier.PRIVATE)
.or(this.and(Modifier.PROTECTED, Modifier.PROTECTED))
.or(this.and(Modifier.PUBLIC, Modifier.PUBLIC));
} else
return this.and(accessModif, 0);
}
}
public final ModifierMatcher forceModifier(int modifier) {
return this.and(modifier, modifier);
}
public final ModifierMatcher forbidModifier(int modifier) {
return this.and(modifier, 0);
}
public final boolean matches(int modifiers) {
for (int i=0; i< andMasks.length; i++)
if ((modifiers & andMasks[i]) == xorMasks[i])
return true;
return false;
}
public final boolean matches(Identifier ident) {
int modifiers;
/* XXX NEW INTERFACE OR ANOTHER METHOD IN IDENTIFIER? */
if (ident instanceof ClassIdentifier)
modifiers = ((ClassIdentifier) ident).info.getModifiers();
else if (ident instanceof MethodIdentifier)
modifiers = ((MethodIdentifier) ident).info.getModifiers();
else if (ident instanceof FieldIdentifier)
modifiers = ((FieldIdentifier) ident).info.getModifiers();
else
return false;
return matches(modifiers);
}
public final boolean matchesSub(Identifier ident, String name) {
return true;
}
public final String getNextComponent(Identifier ident) {
return null;
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException ex) {
throw new IncompatibleClassChangeError(getClass().getName());
}
}
}

@ -1,109 +0,0 @@
/* AndIdentifierMatcher 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;
import @COLLECTIONS@.Collection;
public class MultiIdentifierMatcher implements IdentifierMatcher, OptionHandler {
/**
* Useful constant for giving to the constructor.
*/
public static boolean OR = true;
/**
* Useful constant for giving to the constructor.
*/
public static boolean AND = false;
IdentifierMatcher[] matchers;
boolean isOr;
/**
* Create an empty MultiIdentifierMatcher.
*/
public MultiIdentifierMatcher() {
this.matchers = new IdentifierMatcher[0];
}
/**
* Create an IdentifierMatcher out of other matchers.
* @param isOr if true, match should return the logical (shortcut)
* or of the underlying matchers, if false it returns the logical and.
* @param matchers the underlying matchers
*/
public MultiIdentifierMatcher(boolean isOr,
IdentifierMatcher[] matchers) {
this.isOr = isOr;
this.matchers = matchers;
}
public void setOption(String option, Collection values) {
if (option.equals("or")) {
isOr = true;
matchers = (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]);
} else if (option.equals("and")) {
isOr = false;
matchers = (IdentifierMatcher[])
values.toArray(new IdentifierMatcher[values.size()]);
} else
throw new IllegalArgumentException("Invalid option `"+option+"'.");
}
public boolean matches(Identifier ident) {
for (int i=0; i< matchers.length; i++) {
if (matchers[i].matches(ident) == isOr)
return isOr;
}
return !isOr;
}
public boolean matchesSub(Identifier ident, String name) {
for (int i=0; i< matchers.length; i++) {
if (matchers[i].matchesSub(ident, name) == isOr)
return isOr;
}
return !isOr;
}
public String getNextComponent(Identifier ident) {
if (isOr == AND) {
for (int i=0; i< matchers.length; i++) {
String next = matchers[i].getNextComponent(ident);
if (next != null && matchesSub(ident, next))
return next;
}
return null;
}
// OR case
String next = null;
for (int i = 0; i < matchers.length; i++) {
if (!matchesSub(ident, null))
continue;
if (next != null
&& matchers[i].getNextComponent(ident) != next)
return null;
next = matchers[i].getNextComponent(ident);
if (next == null)
return null;
}
return next;
}
}

@ -1,98 +0,0 @@
/* NameSwapper 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;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.Set;
import @COLLECTIONS@.HashSet;
import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.Random;
import @COLLECTIONEXTRA@.UnsupportedOperationException;
public class NameSwapper implements Renamer {
private Random rand;
private Set packs, clazzes, methods, fields, locals;
public NameSwapper(boolean swapAll, long seed) {
if (swapAll) {
packs = clazzes = methods = fields = locals = new HashSet();
} else {
packs = new HashSet();
clazzes = new HashSet();
methods = new HashSet();
fields = new HashSet();
locals = new HashSet();
}
}
public NameSwapper(boolean swapAll) {
this(swapAll, System.currentTimeMillis());
}
private class NameGenerator implements Iterator {
Collection pool;
NameGenerator(Collection c) {
pool = c;
}
public boolean hasNext() {
return true;
}
public Object next() {
int pos = rand.nextInt(pool.size());
Iterator i = pool.iterator();
while (pos > 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));
}
}

@ -32,6 +32,8 @@ import java.util.zip.ZipOutputStream;
import @COLLECTIONS@.Map;
import @COLLECTIONS@.HashMap;
import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.List;
import @COLLECTIONS@.ArrayList;
public class PackageIdentifier extends Identifier {
ClassBundle bundle;
@ -248,8 +250,9 @@ public class PackageIdentifier extends Identifier {
}
}
}
for (Iterator i = loadedClasses.values().iterator();
i.hasNext(); ) {
List list = new ArrayList();
list.addAll(loadedClasses.values());
for (Iterator i = list.iterator(); i.hasNext(); ) {
Identifier ident = (Identifier) i.next();
if (ident instanceof PackageIdentifier) {
if (matcher.matches(ident))

@ -1,307 +0,0 @@
/* 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;
import jode.bytecode.*;
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;
}
}
}
}

@ -204,7 +204,8 @@ public class ScriptParser {
throw new ParseException(linenr, "Class name expected");
Object instance;
try {
Class clazz = Class.forName("jode.obfuscator."+scanner.getValue());
Class clazz = Class.forName("jode.obfuscator.modules."
+scanner.getValue());
instance = clazz.newInstance();
} catch (ClassNotFoundException ex) {
throw new ParseException(scanner.getLineNr(),

@ -1,182 +0,0 @@
/* 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;
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.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;
}
}
}
}
}
}

@ -1,141 +0,0 @@
/* 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;
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 = option.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 = option.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 <name.length; i++)
name[i] = firstCont;
return new String(name);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

@ -1,41 +0,0 @@
/* UniqueRenamer 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;
import @COLLECTIONS@.Iterator;
import @COLLECTIONEXTRA@.UnsupportedOperationException;
public class UniqueRenamer implements Renamer {
static int serialnr = 0;
public Iterator generateNames(Identifier ident) {
return new Iterator() {
public boolean hasNext() {
return true;
}
public Object next() {
return "xxx" + serialnr++;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

@ -1,108 +0,0 @@
/* WildCard 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;
import @COLLECTIONS@.Collection;
public class WildCard implements IdentifierMatcher, OptionHandler {
String wildcard;
int firstStar;
public WildCard() {
}
public WildCard(String wild) {
wildcard = wild;
firstStar = wildcard.indexOf('*');
}
public void setOption(String option, Collection values) {
if (option.equals("value")) {
if (values.size() != 1)
throw new IllegalArgumentException
("Wildcard supports only one value.");
wildcard = (String) values.iterator().next();
firstStar = wildcard.indexOf('*');
} else
throw new IllegalArgumentException("Invalid option `"+option+"'.");
}
public String getNextComponent(Identifier ident) {
String prefix = ident.getFullName();
if (prefix.length() > 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;
}
}
Loading…
Cancel
Save