OuterValues added

inner/anonymous classes completely reworked


git-svn-id: https://svn.code.sf.net/p/jode/code/trunk@1191 379699f6-c40d-0410-875b-85095c16579e
branch_1_1
jochen 25 years ago
parent 2ad9965256
commit 62ca43d74f
  1. 113
      jode/jode/decompiler/ClassAnalyzer.java.in
  2. 1
      jode/jode/decompiler/Makefile.am
  3. 373
      jode/jode/decompiler/MethodAnalyzer.java.in
  4. 11
      jode/jode/decompiler/OuterValueListener.java
  5. 353
      jode/jode/decompiler/OuterValues.java

@ -31,6 +31,7 @@ import jode.expr.Expression;
import jode.expr.ThisOperator; import jode.expr.ThisOperator;
import jode.flow.TransformConstructors; import jode.flow.TransformConstructors;
import jode.flow.StructuredBlock; import jode.flow.StructuredBlock;
import jode.flow.FlowBlock;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
@ -38,6 +39,8 @@ import java.util.Vector;
import java.util.Enumeration; import java.util.Enumeration;
import java.io.IOException; import java.io.IOException;
import @COLLECTIONS@.Collection;
public class ClassAnalyzer public class ClassAnalyzer
implements Analyzer, Scope, Declarable, ClassDeclarer implements Analyzer, Scope, Declarable, ClassDeclarer
{ {
@ -46,7 +49,7 @@ public class ClassAnalyzer
ClassDeclarer parent; ClassDeclarer parent;
String name; String name;
StructuredBlock[] blockInitializers; FlowBlock[] blockInitializers;
FieldAnalyzer[] fields; FieldAnalyzer[] fields;
MethodAnalyzer[] methods; MethodAnalyzer[] methods;
ClassAnalyzer[] inners; ClassAnalyzer[] inners;
@ -56,36 +59,18 @@ public class ClassAnalyzer
MethodAnalyzer staticConstructor; MethodAnalyzer staticConstructor;
MethodAnalyzer[] constructors; MethodAnalyzer[] constructors;
/** OuterValues outerValues;
* outerValues are used in method scoped classes: If a method
* scoped class uses a local of the surrounding method, the java
* compiler adds the locals to the param list of each constructor
* of the method scoped class. Each invocation of the constructor
* must give the correct values for these locals.
*
* These extra parameters are the outerValues.
*
* The main problem here is, that we don't know immediately if a
* parameter is a standard parameter or a local of the outer
* method. We may shrink this array if we notice a problem later.
*
* @see #shrinkOuterValues
* @see #addOuterValueListener
*/
Expression[] outerValues;
boolean jikesAnonymousInner = false;
Vector ovListeners;
public ClassAnalyzer(ClassDeclarer parent, public ClassAnalyzer(ClassDeclarer parent,
ClassInfo clazz, ImportHandler imports, ClassInfo clazz, ImportHandler imports,
Expression[] outerValues) Expression[] outerValues)
{ {
clazz.loadInfo(clazz.FULLINFO); clazz.loadInfo(clazz.MOSTINFO);
this.parent = parent; this.parent = parent;
this.clazz = clazz; this.clazz = clazz;
this.imports = imports; this.imports = imports;
this.outerValues = outerValues; if (outerValues != null)
this.outerValues = new OuterValues(this, outerValues);
modifiers = clazz.getModifiers(); modifiers = clazz.getModifiers();
if (parent != null) { if (parent != null) {
@ -129,11 +114,15 @@ public class ClassAnalyzer
return Modifier.isStatic(modifiers); return Modifier.isStatic(modifiers);
} }
public FieldAnalyzer getField(String fieldName, Type fieldType) { public FieldAnalyzer getField(int index) {
return fields[index];
}
public int getFieldIndex(String fieldName, Type fieldType) {
for (int i=0; i< fields.length; i++) { for (int i=0; i< fields.length; i++) {
if (fields[i].getName().equals(fieldName) if (fields[i].getName().equals(fieldName)
&& fields[i].getType().equals(fieldType)) && fields[i].getType().equals(fieldType))
return fields[i]; return i;
} }
throw new NoSuchElementException throw new NoSuchElementException
("Field "+fieldType+" "+clazz.getName()+"."+fieldName); ("Field "+fieldType+" "+clazz.getName()+"."+fieldName);
@ -173,57 +162,15 @@ public class ClassAnalyzer
this.name = name; this.name = name;
} }
public Expression[] getOuterValues() { public OuterValues getOuterValues() {
return outerValues; return outerValues;
} }
public void addBlockInitializer(FieldAnalyzer nextField, public void addBlockInitializer(int index,
StructuredBlock initializer) { StructuredBlock initializer) {
int index = 0; if (blockInitializers[index] == null)
while (index < fields.length) { blockInitializers[index] = new FlowBlock(null, 0);
if (fields[index] == nextField) blockInitializers[index].appendBlock(initializer, 0);
break;
index++;
}
blockInitializers[index] = initializer;
}
public void addOuterValueListener(OuterValueListener l) {
if (ovListeners == null)
ovListeners = new Vector();
ovListeners.addElement(l);
}
public void shrinkOuterValues(int newCount) {
if (newCount >= outerValues.length)
return;
Expression[] newOuter = new Expression[newCount];
System.arraycopy(outerValues, 0, newOuter, 0, newCount);
outerValues = newOuter;
if (ovListeners != null) {
for (Enumeration enum = ovListeners.elements();
enum.hasMoreElements();)
((OuterValueListener) enum.nextElement()
).shrinkingOuterValues(this, newCount);
}
}
/**
* Jikes gives the outer class reference in an unusual place (as last
* parameter) for anonymous classes that extends an inner (or method
* scope) class. This method tells if this is such a class.
*/
public void setJikesAnonymousInner(boolean value) {
jikesAnonymousInner = value;
}
/**
* Jikes gives the outer class reference in an unusual place (as last
* parameter) for anonymous classes that extends an inner (or method
* scope) class. This method tells if this is such a class.
*/
public boolean isJikesAnonymousInner() {
return jikesAnonymousInner;
} }
public void initialize() { public void initialize() {
@ -259,7 +206,7 @@ public class ClassAnalyzer
fields = new FieldAnalyzer[finfos.length]; fields = new FieldAnalyzer[finfos.length];
methods = new MethodAnalyzer[minfos.length]; methods = new MethodAnalyzer[minfos.length];
blockInitializers = new StructuredBlock[finfos.length+1]; blockInitializers = new FlowBlock[finfos.length+1];
for (int j=0; j < finfos.length; j++) for (int j=0; j < finfos.length; j++)
fields[j] = new FieldAnalyzer(this, finfos[j], imports); fields[j] = new FieldAnalyzer(this, finfos[j], imports);
@ -279,10 +226,6 @@ public class ClassAnalyzer
constructors = new MethodAnalyzer[constrVector.size()]; constructors = new MethodAnalyzer[constrVector.size()];
constrVector.copyInto(constructors); constrVector.copyInto(constructors);
// // initialize the methods.
// for (int j=0; j < methods.length; j++)
// methods[j].initialize();
// initialize the inner classes. // initialize the inner classes.
for (int j=0; j < inners.length; j++) { for (int j=0; j < inners.length; j++) {
inners[j].initialize(); inners[j].initialize();
@ -313,7 +256,7 @@ public class ClassAnalyzer
for (int j=0; j< constructors.length; j++) for (int j=0; j< constructors.length; j++)
constructors[j].analyze(); constructors[j].analyze();
constrAna = new TransformConstructors(this, false, constructors); constrAna = new TransformConstructors(this, false, constructors);
constrAna.initSyntheticFields(); constrAna.removeSynthInitializers();
} }
if (staticConstructor != null) if (staticConstructor != null)
staticConstructor.analyze(); staticConstructor.analyze();
@ -390,7 +333,11 @@ public class ClassAnalyzer
if (blockInitializers[i] != null) { if (blockInitializers[i] != null) {
if (needNewLine) if (needNewLine)
writer.println(""); writer.println("");
writer.openBrace();
writer.tab();
blockInitializers[i].dumpSource(writer); blockInitializers[i].dumpSource(writer);
writer.untab();
writer.closeBrace();
needFieldNewLine = needNewLine = true; needFieldNewLine = needNewLine = true;
} }
if ((Decompiler.options & Decompiler.OPTION_IMMEDIATE) != 0) { if ((Decompiler.options & Decompiler.OPTION_IMMEDIATE) != 0) {
@ -588,7 +535,7 @@ public class ClassAnalyzer
* class with the given name. * class with the given name.
*/ */
public ClassAnalyzer getInnerClassAnalyzer(String name) { public ClassAnalyzer getInnerClassAnalyzer(String name) {
/** precondition name != null; **/ /** require name != null; **/
int innerCount = inners.length; int innerCount = inners.length;
for (int i=0; i < innerCount; i++) { for (int i=0; i < innerCount; i++) {
if (inners[i].name.equals(name)) if (inners[i].name.equals(name))
@ -597,6 +544,14 @@ public class ClassAnalyzer
return null; return null;
} }
/**
* We add the named method scoped classes to the declarables.
*/
public void fillDeclarables(Collection used) {
for (int j=0; j < methods.length; j++)
methods[j].fillDeclarables(used);
}
public void addClassAnalyzer(ClassAnalyzer clazzAna) { public void addClassAnalyzer(ClassAnalyzer clazzAna) {
if (parent != null) if (parent != null)
parent.addClassAnalyzer(clazzAna); parent.addClassAnalyzer(clazzAna);

@ -25,6 +25,7 @@ MY_JAVA_FILES = \
MethodAnalyzer.java \ MethodAnalyzer.java \
Opcodes.java \ Opcodes.java \
OuterValueListener.java \ OuterValueListener.java \
OuterValues.java \
Scope.java \ Scope.java \
TabbedPrintWriter.java TabbedPrintWriter.java

@ -54,6 +54,8 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import @COLLECTIONS@.Map; import @COLLECTIONS@.Map;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.ArrayList;
import @COLLECTIONS@.Iterator; import @COLLECTIONS@.Iterator;
/** /**
@ -140,20 +142,22 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
LocalVariableTable lvt; LocalVariableTable lvt;
/** /**
* True, iff this method is the special constructor, that is generated * If this method is the special constructor, that is generated
* by jikes (constructor$xx) * by jikes (constructor$xx), this points to the real constructor.
* If this is the real constructor and calls a constructor$xx, it
* points to this. Otherwise this is null.
*/ */
boolean isJikesConstructor; MethodAnalyzer jikesConstructor;
/** /**
* True, iff this method is the special constructor, and its first * True, iff this method is the special constructor, and its first
* parameter is a reference to the outer class. * parameter is a reference to the outer class.
*/ */
boolean hasJikesOuterValue; boolean hasJikesOuterValue;
/** /**
* True, iff this method is an implicit constructor, that may be (or even * True, iff this method is an anonymous constructor, that is
* must be for anonymous classes) omitted. * omitted even if it has parameters.
*/ */
boolean isImplicitAnonymousConstructor; boolean isAnonymousConstructor;
/** /**
* True, if this method is the special block$ method generated by jikes * True, if this method is the special block$ method generated by jikes
* to initialize field members. * to initialize field members.
@ -167,9 +171,15 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
/** /**
* This list contains the class analyzers of all method scoped * This list contains the class analyzers of all method scoped
* classes that should be declared in this method. * classes that should be declared in this method or in a class
* that is declared in this method.
*/ */
Vector innerAnalyzers; Vector innerAnalyzers;
/**
* This list contains the class analyzers of all method scoped
* classes that are used in this method.
*/
Collection usedAnalyzers;
/** /**
* This is the default constructor. * This is the default constructor.
@ -324,8 +334,8 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
* Tells if this method is the constructor$xx method generated by jikes. * Tells if this method is the constructor$xx method generated by jikes.
* @param value true, iff this method is the jikes constructor. * @param value true, iff this method is the jikes constructor.
*/ */
public final void setJikesConstructor(boolean value) { public final void setJikesConstructor(MethodAnalyzer realConstr) {
isJikesConstructor = value; jikesConstructor = realConstr;
} }
/** /**
@ -350,7 +360,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
* @param value true, this method is the implicit constructor. * @param value true, this method is the implicit constructor.
*/ */
public final void setAnonymousConstructor(boolean value) { public final void setAnonymousConstructor(boolean value) {
isImplicitAnonymousConstructor = value; isAnonymousConstructor = value;
} }
/** /**
@ -358,7 +368,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
* @return true, this method is the implicit constructor. * @return true, this method is the implicit constructor.
*/ */
public final boolean isAnonymousConstructor() { public final boolean isAnonymousConstructor() {
return isImplicitAnonymousConstructor; return isAnonymousConstructor;
} }
/** /**
@ -392,7 +402,8 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
} }
/** /**
* Get the local info for a parameter. * Get the local info for a parameter. This call is valid after
* the analyze pass.
* @param nr the index of the parameter (start by zero and * @param nr the index of the parameter (start by zero and
* count the implicit this param for nonstatic method). * count the implicit this param for nonstatic method).
* @return the local info for the specified parameter. * @return the local info for the specified parameter.
@ -402,6 +413,14 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
return param[nr]; return param[nr];
} }
/**
* Return the number of parameters for this method. This call is
* valid after the analyze pass.
*/
public final int getParamCount() {
return param.length;
}
/** /**
* Create a local info for a local variable located at an * Create a local info for a local variable located at an
* instruction with the given address. * instruction with the given address.
@ -485,7 +504,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
if (lastSequential && instr.getTmpInfo() == null if (lastSequential && instr.getTmpInfo() == null
/* Only merge with previous block, if this is sequential, /* Only merge with previous block, if this is sequential,
* too. * too.
* Why? appendBlock does only handle sequential blocks. * Why? appendBlock only handles sequential blocks.
*/ */
&& !instr.doesAlwaysJump() && instr.getSuccs() == null) { && !instr.doesAlwaysJump() && instr.getSuccs() == null) {
@ -589,7 +608,12 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
public void analyzeInnerClasses() public void analyzeInnerClasses()
throws ClassFormatError throws ClassFormatError
{ {
createAnonymousClasses(); int serialnr = 0;
Enumeration elts = anonConstructors.elements();
while (elts.hasMoreElements()) {
InvokeOperator cop = (InvokeOperator) elts.nextElement();
analyzeInvokeOperator(cop);
}
} }
/** /**
@ -598,20 +622,6 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
* It will also determine where to declare method scoped local variables. * It will also determine where to declare method scoped local variables.
*/ */
public void makeDeclaration() { public void makeDeclaration() {
if (isConstructor() && !isStatic()
&& classAnalyzer.outerValues != null
&& (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0) {
Expression[] outerValues = classAnalyzer.outerValues;
for (int i=0; i< outerValues.length; i++) {
LocalInfo local = param[1+i];
local.setExpression(outerValues[i]);
}
}
if (isJikesConstructor && hasJikesOuterValue
&& classAnalyzer.outerValues != null
&& classAnalyzer.outerValues.length > 0)
param[1].setExpression(classAnalyzer.outerValues[0]);
for (Enumeration enum = allLocals.elements(); for (Enumeration enum = allLocals.elements();
enum.hasMoreElements(); ) { enum.hasMoreElements(); ) {
LocalInfo li = (LocalInfo)enum.nextElement(); LocalInfo li = (LocalInfo)enum.nextElement();
@ -639,11 +649,12 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
enum.hasMoreElements(); ) { enum.hasMoreElements(); ) {
ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement(); ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
if (classAna.getParent() == this) { if (classAna.getParent() == this) {
Expression[] outerValues = classAna.getOuterValues(); OuterValues innerOV = classAna.getOuterValues();
for (int i=0; i < outerValues.length; i++) { for (int i=0; i < innerOV.getCount(); i++) {
if (outerValues[i] instanceof OuterLocalOperator) { Expression value = innerOV.getValue(i);
if (value instanceof OuterLocalOperator) {
LocalInfo li = ((OuterLocalOperator) LocalInfo li = ((OuterLocalOperator)
outerValues[i]).getLocalInfo(); value).getLocalInfo();
if (li.getMethodAnalyzer() == this) if (li.getMethodAnalyzer() == this)
li.markFinal(); li.markFinal();
} }
@ -665,13 +676,13 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
if (synth.getKind() == synth.GETCLASS) if (synth.getKind() == synth.GETCLASS)
return true; return true;
if (synth.getKind() >= synth.ACCESSGETFIELD if (synth.getKind() >= synth.ACCESSGETFIELD
&& synth.getKind() <= synth.ACCESSSTATICMETHOD && synth.getKind() <= synth.ACCESSCONSTRUCTOR
&& (Decompiler.options & Decompiler.OPTION_INNER) != 0 && (Decompiler.options & Decompiler.OPTION_INNER) != 0
&& (Decompiler.options & Decompiler.OPTION_ANON) != 0) && (Decompiler.options & Decompiler.OPTION_ANON) != 0)
return true; return true;
} }
if (isConstructor && isJikesConstructor) { if (jikesConstructor == this) {
// This is the first empty part of a jikes constructor // This is the first empty part of a jikes constructor
return true; return true;
} }
@ -680,18 +691,24 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
int skipParams = 0; int skipParams = 0;
if (isConstructor() && !isStatic() if (isConstructor() && !isStatic()
&& classAnalyzer.outerValues != null) && classAnalyzer.outerValues != null)
skipParams = classAnalyzer.outerValues.length; skipParams = classAnalyzer.outerValues.getCount();
if (isJikesConstructor) { if (jikesConstructor != null) {
// This is the real part of a jikes constructor // This is the real part of a jikes constructor
declareAsConstructor = true; declareAsConstructor = true;
skipParams = hasJikesOuterValue skipParams = hasJikesOuterValue
&& classAnalyzer.outerValues.length > 0 ? 1 : 0; && classAnalyzer.outerValues.getCount() > 0 ? 1 : 0;
} }
if (isJikesBlockInitializer) if (isJikesBlockInitializer)
return true; return true;
/* The default constructor must be empty of course */
if (getMethodHeader() == null
|| !(getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock)
|| !getMethodHeader().hasNoJumps())
return false;
if (declareAsConstructor if (declareAsConstructor
/* The access rights of default constructor should /* The access rights of default constructor should
* be public, iff the class is public, otherwise package. * be public, iff the class is public, otherwise package.
@ -699,33 +716,22 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
* classes... * classes...
*/ */
&& ((minfo.getModifiers() && ((minfo.getModifiers()
& (Modifier.PROTECTED | Modifier.PUBLIC)) & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE
| Modifier.SYNCHRONIZED | Modifier.STATIC
| Modifier.ABSTRACT | Modifier.NATIVE))
== (getClassAnalyzer().getModifiers() == (getClassAnalyzer().getModifiers()
& (Modifier.PROTECTED | Modifier.PUBLIC)) & (Modifier.PROTECTED | Modifier.PUBLIC))
|| classAnalyzer.getName() == null) || classAnalyzer.getName() == null)
&& (minfo.getModifiers()
& (Modifier.PRIVATE
| Modifier.SYNCHRONIZED | Modifier.STATIC
| Modifier.ABSTRACT | Modifier.NATIVE)) == 0
&& classAnalyzer.constructors.length == 1) { && classAnalyzer.constructors.length == 1) {
// If this is the only constructor and it is empty and // If the constructor doesn't take parameters (except outerValues)
// takes no parameters, this is the default constructor. // or if it is the anonymous constructor it can be removed.
if (methodType.getParameterTypes().length == skipParams if (methodType.getParameterTypes().length == skipParams
&& getMethodHeader() != null || isAnonymousConstructor)
&& getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock
&& getMethodHeader().hasNoJumps())
return true;
// if this is an anonymous class and this is the only
// constructor and it only does a super call with the given
// parameters, this is constructor is implicit.
if (isImplicitAnonymousConstructor)
return true; return true;
} }
if (isConstructor() && isStatic() if (isConstructor() && isStatic())
&& getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock)
return true; return true;
return false; return false;
@ -741,16 +747,20 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
{ {
boolean declareAsConstructor = isConstructor; boolean declareAsConstructor = isConstructor;
int skipParams = 0; int skipParams = 0;
int modifiedModifiers = minfo.getModifiers();
if (isConstructor() && !isStatic() if (isConstructor() && !isStatic()
&& (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0 && (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0
&& classAnalyzer.outerValues != null) && classAnalyzer.outerValues != null)
skipParams = classAnalyzer.outerValues.length; skipParams = classAnalyzer.outerValues.getCount();
if (isJikesConstructor) { if (jikesConstructor != null) {
// This is the real part of a jikes constructor // This is the real part of a jikes constructor
declareAsConstructor = true; declareAsConstructor = true;
skipParams = hasJikesOuterValue skipParams = hasJikesOuterValue
&& classAnalyzer.outerValues.length > 0 ? 1 : 0; && classAnalyzer.outerValues.getCount() > 0 ? 1 : 0;
// get the modifiers of the real constructor
modifiedModifiers = jikesConstructor.minfo.getModifiers();
} }
if (minfo.isDeprecated()) { if (minfo.isDeprecated()) {
@ -765,7 +775,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
&& (classAnalyzer.getName() != null && (classAnalyzer.getName() != null
|| !isConstructor())) || !isConstructor()))
writer.print("/*synthetic*/ "); writer.print("/*synthetic*/ ");
int modifiedModifiers = minfo.getModifiers();
/* /*
* JLS-1.0, section 9.4: * JLS-1.0, section 9.4:
* *
@ -911,190 +921,25 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
anonConstructors.addElement(cop); anonConstructors.addElement(cop);
} }
private boolean unifyOuterValues(Expression ov1, Expression ov2,
final ClassAnalyzer clazzAna,
final int shrinkTo) {
/* Wow, unifying outer values of different constructors in
* different methods of different classes can get complicated.
* We have not committed the number of OuterValues. So we
* can't say for sure, if the local load matches an outer
* local if this is a constructor. Even worse: The previous
* outerValues may be a load of a constructor local, that
* should be used as outer value...
*
* We look if there is a way to merge them and register an
* outer value listener to lots of classes.
*/
LocalInfo li1 = null;
MethodAnalyzer method1 = null;
if (ov2 instanceof ThisOperator) {
if (ov1 instanceof ThisOperator)
return ov1.equals(ov2);
Expression temp = ov2;
ov2 = ov1;
ov1 = temp;
} else {
if (ov1 instanceof LocalLoadOperator)
li1 = ((LocalLoadOperator) ov1).getLocalInfo();
else if (ov1 instanceof OuterLocalOperator)
li1 = ((OuterLocalOperator) ov1).getLocalInfo();
else if (!(ov1 instanceof ThisOperator))
return false;
}
LocalInfo li2;
if (ov2 instanceof LocalLoadOperator)
li2 = ((LocalLoadOperator) ov2).getLocalInfo();
else if (ov2 instanceof OuterLocalOperator)
li2 = ((OuterLocalOperator) ov2).getLocalInfo();
else
return false;
MethodAnalyzer method2 = li2.getMethodAnalyzer();
/* Now: li2 != null, method2 != null
* (li1 == null and method1 == null) iff ov1 is ThisOperator
*/
class ShrinkOnShrink implements OuterValueListener {
Map limits = new SimpleMap();
public void setLimit(ClassAnalyzer other,
int newLimit) {
limits.put(other, new Integer(newLimit));
other.addOuterValueListener(this);
}
public void done() {
limits = null;
}
public void shrinkingOuterValues
(ClassAnalyzer other, int newCount) {
if (limits != null) {
int limit = ((Integer) limits.get(other)
).intValue();
if (newCount <= limit) {
clazzAna.shrinkOuterValues(shrinkTo);
done();
}
}
}
}
ShrinkOnShrink sos = new ShrinkOnShrink();
if (li1 != null) {
method1 = li1.getMethodAnalyzer();
while (!method2.isParent(method1)) {
if (!method1.isConstructor() || method1.isStatic()) {
sos.done();
return false;
}
ClassAnalyzer ca1 = method1.classAnalyzer;
int slot = li1.getSlot();
Expression[] ov = ca1.getOuterValues();
if (ov == null) {
sos.done();
return false;
}
int param = 0;
while (param < ov.length && slot > 0)
slot -= ov[param++].getType().stackSize();
if (slot != 0) {
sos.done();
return false;
}
ov1 = ov[param];
sos.setLimit(ca1, param);
if (ov1 instanceof ThisOperator) {
li1 = null;
method1 = null;
break;
}
li1 = ((OuterLocalOperator) ov1).getLocalInfo();
method1 = li1.getMethodAnalyzer();
}
}
/* Now: ov1 is ThisOperator and method1 == null
* or (ov1 is LocalExpression, li1 is LocalInfo,
* method1 is parent of method2).
*/
while (method1 != method2) {
if (!method2.isConstructor() || method2.isStatic()) {
sos.done();
return false;
}
ClassAnalyzer ca2 = method2.classAnalyzer;
int slot = li2.getSlot();
Expression[] ov = ca2.getOuterValues();
if (ov == null) {
sos.done();
return false;
}
slot--;
int param = 0;
while (param < ov.length && slot > 0)
slot -= ov[param++].getType().stackSize();
if (slot != 0) {
sos.done();
return false;
}
ov2 = ov[param];
sos.setLimit(ca2, param);
if (ov2 instanceof ThisOperator) {
if (ov1.equals(ov2))
return true;
else {
sos.done();
return false;
}
}
li2 = ((OuterLocalOperator) ov2).getLocalInfo();
method2 = li2.getMethodAnalyzer();
}
if (!li1.equals(li2)) {
sos.done();
return false;
}
return true;
}
public void analyzeInvokeOperator(InvokeOperator cop) { public void analyzeInvokeOperator(InvokeOperator cop) {
ClassInfo clazz = (ClassInfo) cop.getClassInfo(); ClassInfo clazz = (ClassInfo) cop.getClassInfo();
ClassAnalyzer anonAnalyzer = getParent().getClassAnalyzer(clazz); ClassAnalyzer anonAnalyzer = getParent().getClassAnalyzer(clazz);
Expression[] outerValues;
if (anonAnalyzer == null) { if (anonAnalyzer == null) {
/* Create a new outerValues array corresponding to the /* Create a new outerValues array corresponding to the
* first constructor invocation. * first constructor invocation.
*/ */
Expression[] outerValueArray;
Expression[] subExprs = cop.getSubExpressions(); Expression[] subExprs = cop.getSubExpressions();
outerValues = new Expression[subExprs.length-1]; outerValueArray = new Expression[subExprs.length-1];
for (int j=0; j < outerValues.length; j++) { for (int j=0; j < outerValueArray.length; j++) {
Expression expr = subExprs[j+1].simplify(); Expression expr = subExprs[j+1].simplify();
if (expr instanceof CheckNullOperator) if (expr instanceof CheckNullOperator)
expr = ((CheckNullOperator) expr = ((CheckNullOperator)
expr).getSubExpressions()[0]; expr).getSubExpressions()[0];
if (expr instanceof ThisOperator) { if (expr instanceof ThisOperator) {
outerValues[j] = outerValueArray[j] =
new ThisOperator(((ThisOperator) new ThisOperator(((ThisOperator)
expr).getClassInfo()); expr).getClassInfo());
continue; continue;
@ -1109,59 +954,53 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
li = ((OuterLocalOperator) expr).getLocalInfo(); li = ((OuterLocalOperator) expr).getLocalInfo();
if (li != null) { if (li != null) {
outerValues[j] = new OuterLocalOperator(li); outerValueArray[j] = new OuterLocalOperator(li);
continue; continue;
} }
Expression[] newOuter = new Expression[j]; Expression[] newOuter = new Expression[j];
System.arraycopy(outerValues, 0, newOuter, 0, j); System.arraycopy(outerValueArray, 0, newOuter, 0, j);
outerValues = newOuter; outerValueArray = newOuter;
break; break;
} }
anonAnalyzer = new ClassAnalyzer(this, clazz, imports, anonAnalyzer = new ClassAnalyzer(this, clazz, imports,
outerValues); outerValueArray);
addClassAnalyzer(anonAnalyzer); addClassAnalyzer(anonAnalyzer);
anonAnalyzer.initialize();
anonAnalyzer.analyze(); anonAnalyzer.analyze();
anonAnalyzer.analyzeInnerClasses(); anonAnalyzer.analyzeInnerClasses();
} else { } else {
/* /*
* Get the previously created outerValues array and * Get the previously created outerValues and
* its length. * its length.
*/ */
outerValues = anonAnalyzer.getOuterValues(); OuterValues outerValues = anonAnalyzer.getOuterValues();
/* /*
* Merge the other constructor invocation and * Merge the other constructor invocation and
* possibly shrink outerValues array. * possibly shrink outerValues array.
*/ */
Expression[] subExprs = cop.getSubExpressions(); Expression[] subExprs = cop.getSubExpressions();
for (int j=0; j < outerValues.length; j++) { for (int j=0; j < outerValues.getCount(); j++) {
if (j+1 < subExprs.length) { if (j+1 < subExprs.length) {
Expression expr = subExprs[j+1].simplify(); Expression expr = subExprs[j+1].simplify();
if (expr instanceof CheckNullOperator) if (expr instanceof CheckNullOperator)
expr = ((CheckNullOperator) expr) expr = ((CheckNullOperator) expr)
.getSubExpressions()[0]; .getSubExpressions()[0];
if (unifyOuterValues(outerValues[j], expr, if (outerValues.unifyOuterValues(j, expr))
anonAnalyzer, j))
continue; continue;
} }
anonAnalyzer.shrinkOuterValues(j); outerValues.setCount(j);
break; break;
} }
} }
if (usedAnalyzers == null)
usedAnalyzers = new ArrayList();
usedAnalyzers.add(anonAnalyzer);
} }
public void createAnonymousClasses() {
int serialnr = 0;
Enumeration elts = anonConstructors.elements();
while (elts.hasMoreElements()) {
InvokeOperator cop = (InvokeOperator) elts.nextElement();
analyzeInvokeOperator(cop);
}
}
/** /**
* Get the class analyzer for the given class info. This searches * Get the class analyzer for the given class info. This searches
* the method scoped/anonymous classes in this method and all * the method scoped/anonymous classes in this method and all
@ -1177,15 +1016,13 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
while (enum.hasMoreElements()) { while (enum.hasMoreElements()) {
ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement(); ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
if (classAna.getClazz().equals(cinfo)) { if (classAna.getClazz().equals(cinfo)) {
if (!isParent(classAna.getParent())) { if (classAna.getParent() != this) {
ClassDeclarer declarer = classAna.getParent();
Expression[] outerValues = classAna.getOuterValues(); while (declarer != this) {
for (int i=0; i< outerValues.length; i++) { if (declarer instanceof MethodAnalyzer)
if (outerValues[i] instanceof OuterLocalOperator) { ((MethodAnalyzer) declarer)
LocalInfo li = ((OuterLocalOperator) .innerAnalyzers.removeElement(classAna);
outerValues[i]).getLocalInfo(); declarer = declarer.getParent();
classAna.shrinkOuterValues(i-1);
}
} }
classAna.setParent(this); classAna.setParent(this);
} }
@ -1203,10 +1040,26 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
getParent().addClassAnalyzer(clazzAna); getParent().addClassAnalyzer(clazzAna);
} }
public boolean isParent(ClassDeclarer declarer) { /**
ClassDeclarer ancestor = this; * We add the named method scoped classes to the declarables.
*/
public void fillDeclarables(Collection used) {
if (usedAnalyzers != null)
used.addAll(usedAnalyzers);
if (innerAnalyzers != null) {
Enumeration enum = innerAnalyzers.elements();
while (enum.hasMoreElements()) {
ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
if (classAna.getParent() == this)
classAna.fillDeclarables(used);
}
}
}
public boolean isMoreOuterThan(ClassDeclarer declarer) {
ClassDeclarer ancestor = declarer;
while (ancestor != null) { while (ancestor != null) {
if (ancestor == declarer) if (ancestor == this)
return true; return true;
ancestor = ancestor.getParent(); ancestor = ancestor.getParent();
} }

@ -21,7 +21,16 @@ package jode.decompiler;
/** /**
* Interface, that every one should implement who is interested in * Interface, that every one should implement who is interested in
* outer value changes. * outer value changes.
*
* outerValues
*/ */
public interface OuterValueListener { public interface OuterValueListener {
public void shrinkingOuterValues(ClassAnalyzer clazzAna, int newLength); /**
* Tells that the guessed number of outerValues was too big and thus
* needs shrinking right now.
* @param clazzAna The clazzAnalyzer for which this info is.
* @param newCount The new number of outer values (not slot number)
* before the first parameter.
*/
public void shrinkingOuterValues(OuterValues ov, int newCount);
} }

@ -0,0 +1,353 @@
/* OuterValues Copyright (C) 1998-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.decompiler;
import jode.GlobalOptions;
import jode.expr.Expression;
import jode.expr.ThisOperator;
import jode.expr.LocalLoadOperator;
import jode.expr.OuterLocalOperator;
import jode.util.SimpleMap;
import jode.type.Type;
import java.util.Vector;
import java.util.Enumeration;
/**
* OuterValues are used in method scoped classes: If a method
* scoped class uses a local of the surrounding method, the java
* compiler adds the locals to the param list of each constructor
* of the method scoped class. Each invocation of the constructor
* must give the correct values for these locals.
*
* These extra parameters are the outerValues.
*
* The main problem here is, that we don't know immediately if a
* parameter is a standard parameter or a local of the outer
* method. We may shrink this array if we notice a problem later.
*
* Every class interested in outer values, may register itself
* as OuterValueListener. It will then be notified every time the
* outer values shrink.
*
* The outer instance of a non static _class_ scoped class is not
* considered as outer value, mainly because it can be changed. With
* jikes method scoped classes also have an outer class instance, but
* that is considered as outer value.
*
* Under jikes anonymous classes that extends class or method scoped
* classes have as last parameter the outer instance of the parent
* class. This should really be the first parameter (just after the
* outerValues), like it is under javac. We mark such classes as
* jikesAnonymousInner. This is done in the initialize() pass.
*
* Under javac the outer class paramter for anonymous classes that
* extends class scoped classes is at the right position, just before
* the other parameters.
*
* @see #shrinkOuterValues
* @see #addOuterValueListener
* @since 1.0.93 */
public class OuterValues
{
private ClassAnalyzer clazzAnalyzer;
private Expression[] head;
private Vector ovListeners;
private boolean jikesAnonymousInner;
/**
* The maximal number of parameters used for outer values.
*/
private int headCount;
/**
* The minimal number of parameters used for outer values.
*/
private int headMinCount;
public OuterValues(ClassAnalyzer ca, Expression[] head) {
this.clazzAnalyzer = ca;
this.head = head;
this.headMinCount = 0;
this.headCount = head.length;
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println("Created OuterValues: "+this);
}
public Expression getValue(int i) {
/** require i < getCount() **/
return head[i];
}
public int getCount() {
return headCount;
}
private int getNumberBySlot(int slot) {
slot--; // skip this parameter (not an outer value)
for (int i=0; slot >= 0 && i < headCount; i++) {
if (slot == 0)
return i;
slot -= head[i].getType().stackSize();
}
return -1;
}
/**
* Get the outer value corresponding to a given slot. This will
* also adjust the minSlot value. This only considers head slots.
* @return index into outerValues array or -1, if not matched.
*/
public Expression getValueBySlot(int slot) {
slot--; // skip this parameter (not an outer value)
for (int i=0; i < headCount; i++) {
if (slot == 0) {
Expression expr = head[i];
if (i >= headMinCount)
headMinCount = i;
return expr;
}
slot -= head[i].getType().stackSize();
}
return null;
}
/**
* If li is a local variable of a constructor, and it could be
* an outer value, return this outer value and mark ourself as
* listener. If that outer value gets invalid later, we shrink
* ourself to the given nr.
* @param expr The expression to lift.
* @param nr The nr of outer values we shrink to, if something
* happens later.
* @return the outer value if the above conditions are true,
* null otherwise.
*/
private Expression liftOuterValue(LocalInfo li, final int nr) {
MethodAnalyzer method = li.getMethodAnalyzer();
if (!method.isConstructor() || method.isStatic())
return null;
OuterValues ov = method.getClassAnalyzer().getOuterValues();
if (ov == null)
return null;
int ovNr = ov.getNumberBySlot(li.getSlot());
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println(" ovNr "+ovNr+","+ov);
if (ovNr < 0 && ov.getCount() >= 1 && ov.isJikesAnonymousInner()) {
/* Second chance if this is a jikesAnonInner class:
* last parameter is this parameter. XXX
*/
Type[] paramTypes = method.getType().getParameterTypes();
int lastSlot = 1;
for (int i=0; i < paramTypes.length - 1; i++)
lastSlot += paramTypes[i].stackSize();
/* jikesAnonInner corresponds to the first outer value */
if (li.getSlot() == lastSlot)
ovNr = 0;
}
if (ovNr < 0)
return null;
if (ov != this || ovNr > nr) {
final int limit = ovNr;
ov.addOuterValueListener(new OuterValueListener() {
public void shrinkingOuterValues
(OuterValues other, int newCount) {
if (newCount <= limit)
setCount(nr);
}
});
}
return ov.head[ovNr];
}
public boolean unifyOuterValues(int nr,
Expression otherExpr) {
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println("unifyOuterValues: "+this+","
+nr+","+otherExpr);
/** require nr < getCount() **/
Expression expr1 = otherExpr;
Expression expr2 = head[nr];
LocalInfo li1;
/* Wow, unifying outer values of different constructors in
* different methods of different classes can get complicated.
* We have not committed the number of OuterValues. So we
* can't say for sure, if the local load matches an outer
* local if this is a constructor. Even worse: The previous
* outerValues may be a load of a constructor local, that
* should be used as outer value...
*
* See MethodScopeTest for examples.
*
* We look if there is a way to merge them and register an
* outer value listener to lots of classes.
*/
if (expr1 instanceof ThisOperator) {
li1 = null;
} else if (expr1 instanceof OuterLocalOperator) {
li1 = ((OuterLocalOperator) expr1).getLocalInfo();
} else if (expr1 instanceof LocalLoadOperator) {
li1 = ((LocalLoadOperator) expr1).getLocalInfo();
} else
return false;
/* First lift expr1 until it is a parent of this class */
while (li1 != null
&& !li1.getMethodAnalyzer().isMoreOuterThan(clazzAnalyzer)) {
expr1 = liftOuterValue(li1, nr);
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println(" lift1 "+li1
+" in "+li1.getMethodAnalyzer()
+" to "+expr1);
if (expr1 instanceof ThisOperator) {
li1 = null;
} else if (expr1 instanceof OuterLocalOperator) {
li1 = ((OuterLocalOperator) expr1).getLocalInfo();
} else
return false;
}
/* Now lift expr2 until expr1 and expr2 are equal */
while (!expr1.equals(expr2)) {
if (expr2 instanceof OuterLocalOperator) {
LocalInfo li2 = ((OuterLocalOperator) expr2).getLocalInfo();
/* if expr1 and expr2 point to same local, we have
* succeeded (note that expr1 may be an LocalLoadOperator)
*/
if (li2.equals(li1))
break;
expr2 = liftOuterValue(li2, nr);
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println(" lift2 "+li2
+" in "+li2.getMethodAnalyzer()
+" to "+expr2);
} else
return false;
}
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0)
GlobalOptions.err.println("unifyOuterValues succeeded.");
return true;
}
/**
* Jikes gives the outer class reference in an unusual place (as last
* parameter) for anonymous classes that extends an inner (or method
* scope) class. This method tells if this is such a class.
*/
public boolean isJikesAnonymousInner() {
return jikesAnonymousInner;
}
public void addOuterValueListener(OuterValueListener l) {
if (ovListeners == null)
ovListeners = new Vector();
ovListeners.addElement(l);
}
/**
* Jikes gives the outer class reference in an unusual place (as last
* parameter) for anonymous classes that extends an inner (or method
* scope) class. This method tells if this is such a class.
*/
public void setJikesAnonymousInner(boolean value) {
jikesAnonymousInner = value;
}
private static int countSlots(Expression[] exprs, int length) {
int slots = 0;
for (int i=0; i < length; i++)
slots += exprs[i].getType().stackSize();
return slots;
}
public void setMinCount(int newMin) {
if (headCount < newMin) {
GlobalOptions.err.println
("WARNING: something got wrong with scoped class "
+clazzAnalyzer.getClazz()+": " +newMin+","+headCount);
new Throwable().printStackTrace(GlobalOptions.err);
headMinCount = headCount;
} else if (newMin > headMinCount)
headMinCount = newMin;
}
public void setCount(int newHeadCount) {
if (newHeadCount >= headCount)
return;
headCount = newHeadCount;
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_CONSTRS) != 0) {
GlobalOptions.err.println("setCount: "+this+","+newHeadCount);
new Throwable().printStackTrace(GlobalOptions.err);
}
if (newHeadCount < headMinCount) {
GlobalOptions.err.println
("WARNING: something got wrong with scoped class "
+clazzAnalyzer.getClazz()+": "
+headMinCount+","+headCount);
new Throwable().printStackTrace(GlobalOptions.err);
headMinCount = newHeadCount;
}
if (ovListeners != null) {
for (Enumeration enum = ovListeners.elements();
enum.hasMoreElements();)
((OuterValueListener) enum.nextElement()
).shrinkingOuterValues(this, newHeadCount);
}
}
public String toString() {
StringBuffer sb = new StringBuffer()
.append(clazzAnalyzer.getClazz())
.append(".OuterValues[");
String comma = "";
int slot = 1;
for (int i=0; i < headCount; i++) {
if (i == headMinCount)
sb.append("<-");
sb.append(comma).append(slot).append(":").append(head[i]);
slot += head[i].getType().stackSize();
comma = ",";
}
if (jikesAnonymousInner)
sb.append("!jikesAnonymousInner");
return sb.append("]").toString();
}
}
Loading…
Cancel
Save