diff --git a/jode/jode/decompiler/ClassAnalyzer.java b/jode/jode/decompiler/ClassAnalyzer.java.in similarity index 84% rename from jode/jode/decompiler/ClassAnalyzer.java rename to jode/jode/decompiler/ClassAnalyzer.java.in index 32952d2..d83b4f5 100644 --- a/jode/jode/decompiler/ClassAnalyzer.java +++ b/jode/jode/decompiler/ClassAnalyzer.java.in @@ -31,6 +31,7 @@ import jode.expr.Expression; import jode.expr.ThisOperator; import jode.flow.TransformConstructors; import jode.flow.StructuredBlock; +import jode.flow.FlowBlock; import java.lang.reflect.Modifier; import java.util.NoSuchElementException; @@ -38,6 +39,8 @@ import java.util.Vector; import java.util.Enumeration; import java.io.IOException; +import @COLLECTIONS@.Collection; + public class ClassAnalyzer implements Analyzer, Scope, Declarable, ClassDeclarer { @@ -46,7 +49,7 @@ public class ClassAnalyzer ClassDeclarer parent; String name; - StructuredBlock[] blockInitializers; + FlowBlock[] blockInitializers; FieldAnalyzer[] fields; MethodAnalyzer[] methods; ClassAnalyzer[] inners; @@ -56,36 +59,18 @@ public class ClassAnalyzer MethodAnalyzer staticConstructor; MethodAnalyzer[] constructors; - /** - * 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; + OuterValues outerValues; public ClassAnalyzer(ClassDeclarer parent, ClassInfo clazz, ImportHandler imports, Expression[] outerValues) { - clazz.loadInfo(clazz.FULLINFO); + clazz.loadInfo(clazz.MOSTINFO); this.parent = parent; this.clazz = clazz; this.imports = imports; - this.outerValues = outerValues; + if (outerValues != null) + this.outerValues = new OuterValues(this, outerValues); modifiers = clazz.getModifiers(); if (parent != null) { @@ -129,11 +114,15 @@ public class ClassAnalyzer 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++) { if (fields[i].getName().equals(fieldName) && fields[i].getType().equals(fieldType)) - return fields[i]; + return i; } throw new NoSuchElementException ("Field "+fieldType+" "+clazz.getName()+"."+fieldName); @@ -173,57 +162,15 @@ public class ClassAnalyzer this.name = name; } - public Expression[] getOuterValues() { + public OuterValues getOuterValues() { return outerValues; } - public void addBlockInitializer(FieldAnalyzer nextField, + public void addBlockInitializer(int index, StructuredBlock initializer) { - int index = 0; - while (index < fields.length) { - if (fields[index] == nextField) - 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; + if (blockInitializers[index] == null) + blockInitializers[index] = new FlowBlock(null, 0); + blockInitializers[index].appendBlock(initializer, 0); } public void initialize() { @@ -259,7 +206,7 @@ public class ClassAnalyzer fields = new FieldAnalyzer[finfos.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++) fields[j] = new FieldAnalyzer(this, finfos[j], imports); @@ -279,10 +226,6 @@ public class ClassAnalyzer constructors = new MethodAnalyzer[constrVector.size()]; constrVector.copyInto(constructors); -// // initialize the methods. -// for (int j=0; j < methods.length; j++) -// methods[j].initialize(); - // initialize the inner classes. for (int j=0; j < inners.length; j++) { inners[j].initialize(); @@ -313,7 +256,7 @@ public class ClassAnalyzer for (int j=0; j< constructors.length; j++) constructors[j].analyze(); constrAna = new TransformConstructors(this, false, constructors); - constrAna.initSyntheticFields(); + constrAna.removeSynthInitializers(); } if (staticConstructor != null) staticConstructor.analyze(); @@ -390,7 +333,11 @@ public class ClassAnalyzer if (blockInitializers[i] != null) { if (needNewLine) writer.println(""); + writer.openBrace(); + writer.tab(); blockInitializers[i].dumpSource(writer); + writer.untab(); + writer.closeBrace(); needFieldNewLine = needNewLine = true; } if ((Decompiler.options & Decompiler.OPTION_IMMEDIATE) != 0) { @@ -588,7 +535,7 @@ public class ClassAnalyzer * class with the given name. */ public ClassAnalyzer getInnerClassAnalyzer(String name) { - /** precondition name != null; **/ + /** require name != null; **/ int innerCount = inners.length; for (int i=0; i < innerCount; i++) { if (inners[i].name.equals(name)) @@ -597,6 +544,14 @@ public class ClassAnalyzer 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) { if (parent != null) parent.addClassAnalyzer(clazzAna); diff --git a/jode/jode/decompiler/Makefile.am b/jode/jode/decompiler/Makefile.am index 0876798..5b1b614 100644 --- a/jode/jode/decompiler/Makefile.am +++ b/jode/jode/decompiler/Makefile.am @@ -25,6 +25,7 @@ MY_JAVA_FILES = \ MethodAnalyzer.java \ Opcodes.java \ OuterValueListener.java \ + OuterValues.java \ Scope.java \ TabbedPrintWriter.java diff --git a/jode/jode/decompiler/MethodAnalyzer.java.in b/jode/jode/decompiler/MethodAnalyzer.java.in index 7f9e830..fa385a4 100644 --- a/jode/jode/decompiler/MethodAnalyzer.java.in +++ b/jode/jode/decompiler/MethodAnalyzer.java.in @@ -54,6 +54,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import @COLLECTIONS@.Map; +import @COLLECTIONS@.Collection; +import @COLLECTIONS@.ArrayList; import @COLLECTIONS@.Iterator; /** @@ -140,20 +142,22 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { LocalVariableTable lvt; /** - * True, iff this method is the special constructor, that is generated - * by jikes (constructor$xx) + * If this method is the special constructor, that is generated + * 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 * parameter is a reference to the outer class. */ boolean hasJikesOuterValue; /** - * True, iff this method is an implicit constructor, that may be (or even - * must be for anonymous classes) omitted. + * True, iff this method is an anonymous constructor, that is + * omitted even if it has parameters. */ - boolean isImplicitAnonymousConstructor; + boolean isAnonymousConstructor; /** * True, if this method is the special block$ method generated by jikes * 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 - * 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; + /** + * This list contains the class analyzers of all method scoped + * classes that are used in this method. + */ + Collection usedAnalyzers; /** * 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. * @param value true, iff this method is the jikes constructor. */ - public final void setJikesConstructor(boolean value) { - isJikesConstructor = value; + public final void setJikesConstructor(MethodAnalyzer realConstr) { + jikesConstructor = realConstr; } /** @@ -350,7 +360,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { * @param value true, this method is the implicit constructor. */ 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. */ 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 * count the implicit this param for nonstatic method). * @return the local info for the specified parameter. @@ -402,6 +413,14 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { 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 * instruction with the given address. @@ -485,7 +504,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { if (lastSequential && instr.getTmpInfo() == null /* Only merge with previous block, if this is sequential, * too. - * Why? appendBlock does only handle sequential blocks. + * Why? appendBlock only handles sequential blocks. */ && !instr.doesAlwaysJump() && instr.getSuccs() == null) { @@ -589,7 +608,12 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { public void analyzeInnerClasses() 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. */ 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(); enum.hasMoreElements(); ) { LocalInfo li = (LocalInfo)enum.nextElement(); @@ -639,11 +649,12 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { enum.hasMoreElements(); ) { ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement(); if (classAna.getParent() == this) { - Expression[] outerValues = classAna.getOuterValues(); - for (int i=0; i < outerValues.length; i++) { - if (outerValues[i] instanceof OuterLocalOperator) { + OuterValues innerOV = classAna.getOuterValues(); + for (int i=0; i < innerOV.getCount(); i++) { + Expression value = innerOV.getValue(i); + if (value instanceof OuterLocalOperator) { LocalInfo li = ((OuterLocalOperator) - outerValues[i]).getLocalInfo(); + value).getLocalInfo(); if (li.getMethodAnalyzer() == this) li.markFinal(); } @@ -665,13 +676,13 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { if (synth.getKind() == synth.GETCLASS) return true; if (synth.getKind() >= synth.ACCESSGETFIELD - && synth.getKind() <= synth.ACCESSSTATICMETHOD + && synth.getKind() <= synth.ACCESSCONSTRUCTOR && (Decompiler.options & Decompiler.OPTION_INNER) != 0 && (Decompiler.options & Decompiler.OPTION_ANON) != 0) return true; } - if (isConstructor && isJikesConstructor) { + if (jikesConstructor == this) { // This is the first empty part of a jikes constructor return true; } @@ -680,18 +691,24 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { int skipParams = 0; if (isConstructor() && !isStatic() && 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 declareAsConstructor = true; skipParams = hasJikesOuterValue - && classAnalyzer.outerValues.length > 0 ? 1 : 0; + && classAnalyzer.outerValues.getCount() > 0 ? 1 : 0; } if (isJikesBlockInitializer) 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 /* The access rights of default constructor should * be public, iff the class is public, otherwise package. @@ -699,33 +716,22 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { * classes... */ && ((minfo.getModifiers() - & (Modifier.PROTECTED | Modifier.PUBLIC)) + & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE + | Modifier.SYNCHRONIZED | Modifier.STATIC + | Modifier.ABSTRACT | Modifier.NATIVE)) == (getClassAnalyzer().getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) || classAnalyzer.getName() == null) - && (minfo.getModifiers() - & (Modifier.PRIVATE - | Modifier.SYNCHRONIZED | Modifier.STATIC - | Modifier.ABSTRACT | Modifier.NATIVE)) == 0 && classAnalyzer.constructors.length == 1) { - // If this is the only constructor and it is empty and - // takes no parameters, this is the default constructor. + // If the constructor doesn't take parameters (except outerValues) + // or if it is the anonymous constructor it can be removed. if (methodType.getParameterTypes().length == skipParams - && getMethodHeader() != null - && 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) + || isAnonymousConstructor) return true; } - if (isConstructor() && isStatic() - && getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock) + if (isConstructor() && isStatic()) return true; return false; @@ -741,16 +747,20 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { { boolean declareAsConstructor = isConstructor; int skipParams = 0; + int modifiedModifiers = minfo.getModifiers(); + if (isConstructor() && !isStatic() && (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0 && 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 declareAsConstructor = true; 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()) { @@ -765,7 +775,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { && (classAnalyzer.getName() != null || !isConstructor())) writer.print("/*synthetic*/ "); - int modifiedModifiers = minfo.getModifiers(); + /* * JLS-1.0, section 9.4: * @@ -911,190 +921,25 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { 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) { ClassInfo clazz = (ClassInfo) cop.getClassInfo(); ClassAnalyzer anonAnalyzer = getParent().getClassAnalyzer(clazz); - Expression[] outerValues; if (anonAnalyzer == null) { /* Create a new outerValues array corresponding to the * first constructor invocation. */ + Expression[] outerValueArray; 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(); if (expr instanceof CheckNullOperator) expr = ((CheckNullOperator) expr).getSubExpressions()[0]; if (expr instanceof ThisOperator) { - outerValues[j] = + outerValueArray[j] = new ThisOperator(((ThisOperator) expr).getClassInfo()); continue; @@ -1109,59 +954,53 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { li = ((OuterLocalOperator) expr).getLocalInfo(); if (li != null) { - outerValues[j] = new OuterLocalOperator(li); + outerValueArray[j] = new OuterLocalOperator(li); continue; } Expression[] newOuter = new Expression[j]; - System.arraycopy(outerValues, 0, newOuter, 0, j); - outerValues = newOuter; + System.arraycopy(outerValueArray, 0, newOuter, 0, j); + outerValueArray = newOuter; break; } anonAnalyzer = new ClassAnalyzer(this, clazz, imports, - outerValues); + outerValueArray); addClassAnalyzer(anonAnalyzer); + anonAnalyzer.initialize(); anonAnalyzer.analyze(); anonAnalyzer.analyzeInnerClasses(); } else { - /* - * Get the previously created outerValues array and + * Get the previously created outerValues and * its length. */ - outerValues = anonAnalyzer.getOuterValues(); + OuterValues outerValues = anonAnalyzer.getOuterValues(); /* * Merge the other constructor invocation and * possibly shrink outerValues array. */ 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) { Expression expr = subExprs[j+1].simplify(); if (expr instanceof CheckNullOperator) expr = ((CheckNullOperator) expr) .getSubExpressions()[0]; - if (unifyOuterValues(outerValues[j], expr, - anonAnalyzer, j)) + if (outerValues.unifyOuterValues(j, expr)) continue; } - anonAnalyzer.shrinkOuterValues(j); + outerValues.setCount(j); 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 * the method scoped/anonymous classes in this method and all @@ -1177,15 +1016,13 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { while (enum.hasMoreElements()) { ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement(); if (classAna.getClazz().equals(cinfo)) { - if (!isParent(classAna.getParent())) { - - Expression[] outerValues = classAna.getOuterValues(); - for (int i=0; i< outerValues.length; i++) { - if (outerValues[i] instanceof OuterLocalOperator) { - LocalInfo li = ((OuterLocalOperator) - outerValues[i]).getLocalInfo(); - classAna.shrinkOuterValues(i-1); - } + if (classAna.getParent() != this) { + ClassDeclarer declarer = classAna.getParent(); + while (declarer != this) { + if (declarer instanceof MethodAnalyzer) + ((MethodAnalyzer) declarer) + .innerAnalyzers.removeElement(classAna); + declarer = declarer.getParent(); } classAna.setParent(this); } @@ -1203,10 +1040,26 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { 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) { - if (ancestor == declarer) + if (ancestor == this) return true; ancestor = ancestor.getParent(); } diff --git a/jode/jode/decompiler/OuterValueListener.java b/jode/jode/decompiler/OuterValueListener.java index f62f251..dcd7caa 100644 --- a/jode/jode/decompiler/OuterValueListener.java +++ b/jode/jode/decompiler/OuterValueListener.java @@ -21,7 +21,16 @@ package jode.decompiler; /** * Interface, that every one should implement who is interested in * outer value changes. + * + * outerValues */ 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); } diff --git a/jode/jode/decompiler/OuterValues.java b/jode/jode/decompiler/OuterValues.java new file mode 100644 index 0000000..580c7f5 --- /dev/null +++ b/jode/jode/decompiler/OuterValues.java @@ -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(); + } +} + +