001: /* OuterValues Copyright (C) 1998-2002 Jochen Hoenicke.
002: *
003: * This program is free software; you can redistribute it and/or modify
004: * it under the terms of the GNU Lesser General Public License as published by
005: * the Free Software Foundation; either version 2, or (at your option)
006: * any later version.
007: *
008: * This program is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
011: * GNU General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public License
014: * along with this program; see the file COPYING.LESSER. If not, write to
015: * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
016: *
017: * $Id: OuterValues.java,v 4.1.2.2 2002/05/28 17:34:03 hoenicke Exp $
018: */
019:
020: package jode.decompiler;
021:
022: import jode.GlobalOptions;
023: import jode.expr.Expression;
024: import jode.expr.ThisOperator;
025: import jode.expr.LocalLoadOperator;
026: import jode.expr.OuterLocalOperator;
027: import jode.util.SimpleMap;
028: import jode.type.Type;
029:
030: import java.util.Vector;
031: import java.util.Enumeration;
032:
033: /**
034: * OuterValues are used in method scoped classes: If a method
035: * scoped class uses a local of the surrounding method, the java
036: * compiler adds the locals to the param list of each constructor
037: * of the method scoped class. Each invocation of the constructor
038: * must give the correct values for these locals.
039: *
040: * These extra parameters are the outerValues.
041: *
042: * The main problem here is, that we don't know immediately if a
043: * parameter is a standard parameter or a local of the outer
044: * method. We may shrink this array if we notice a problem later.
045: *
046: * Every class interested in outer values, may register itself
047: * as OuterValueListener. It will then be notified every time the
048: * outer values shrink.
049: *
050: * The outer instance of a non static _class_ scoped class is not
051: * considered as outer value, mainly because it can be changed. With
052: * jikes method scoped classes also have an outer class instance, but
053: * that is considered as outer value.
054: *
055: * Under jikes anonymous classes that extends class or method scoped
056: * classes have as last parameter the outer instance of the parent
057: * class. This should really be the first parameter (just after the
058: * outerValues), like it is under javac. We mark such classes as
059: * jikesAnonymousInner. This is done in the initialize() pass.
060: *
061: * Under javac the outer class paramter for anonymous classes that
062: * extends class scoped classes is at the right position, just before
063: * the other parameters.
064: *
065: * @see #shrinkOuterValues
066: * @see #addOuterValueListener
067: * @since 1.0.93 */
068: public class OuterValues {
069: private ClassAnalyzer clazzAnalyzer;
070:
071: private Expression[] head;
072: private Vector ovListeners;
073: private boolean jikesAnonymousInner;
074: private boolean implicitOuterClass;
075:
076: /**
077: * The maximal number of parameters used for outer values.
078: */
079: private int headCount;
080: /**
081: * The minimal number of parameters used for outer values.
082: */
083: private int headMinCount;
084:
085: public OuterValues(ClassAnalyzer ca, Expression[] head) {
086: this .clazzAnalyzer = ca;
087: this .head = head;
088: this .headMinCount = 0;
089: this .headCount = head.length;
090: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
091: GlobalOptions.err.println("Created OuterValues: " + this );
092: }
093:
094: public Expression getValue(int i) {
095: /** require i < getCount() **/
096: return head[i];
097: }
098:
099: public int getCount() {
100: return headCount;
101: }
102:
103: private int getNumberBySlot(int slot) {
104: slot--; // skip this parameter (not an outer value)
105: for (int i = 0; slot >= 0 && i < headCount; i++) {
106: if (slot == 0)
107: return i;
108: slot -= head[i].getType().stackSize();
109: }
110: return -1;
111: }
112:
113: /**
114: * Get the outer value corresponding to a given slot. This will
115: * also adjust the minSlot value. This only considers head slots.
116: * @return index into outerValues array or -1, if not matched.
117: */
118: public Expression getValueBySlot(int slot) {
119: slot--; // skip this parameter (not an outer value)
120: for (int i = 0; i < headCount; i++) {
121: if (slot == 0) {
122: Expression expr = head[i];
123: if (i >= headMinCount)
124: headMinCount = i;
125: return expr;
126: }
127: slot -= head[i].getType().stackSize();
128: }
129: return null;
130: }
131:
132: /**
133: * If li is a local variable of a constructor, and it could be
134: * an outer value, return this outer value and mark ourself as
135: * listener. If that outer value gets invalid later, we shrink
136: * ourself to the given nr.
137: * @param expr The expression to lift.
138: * @param nr The nr of outer values we shrink to, if something
139: * happens later.
140: * @return the outer value if the above conditions are true,
141: * null otherwise.
142: */
143: private Expression liftOuterValue(LocalInfo li, final int nr) {
144: MethodAnalyzer method = li.getMethodAnalyzer();
145:
146: if (!method.isConstructor() || method.isStatic())
147: return null;
148: OuterValues ov = method.getClassAnalyzer().getOuterValues();
149: if (ov == null)
150: return null;
151:
152: int ovNr = ov.getNumberBySlot(li.getSlot());
153: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
154: GlobalOptions.err.println(" ovNr " + ovNr + "," + ov);
155: if (ovNr < 0 && ov.getCount() >= 1
156: && ov.isJikesAnonymousInner()) {
157: /* Second chance if this is a jikesAnonInner class:
158: * last parameter is this parameter. XXX
159: */
160: Type[] paramTypes = method.getType().getParameterTypes();
161: int lastSlot = 1;
162: for (int i = 0; i < paramTypes.length - 1; i++)
163: lastSlot += paramTypes[i].stackSize();
164:
165: /* jikesAnonInner corresponds to the first outer value */
166: if (li.getSlot() == lastSlot)
167: ovNr = 0;
168: }
169: if (ovNr < 0)
170: return null;
171: if (ov != this || ovNr > nr) {
172: final int limit = ovNr;
173: ov.addOuterValueListener(new OuterValueListener() {
174: public void shrinkingOuterValues(OuterValues other,
175: int newCount) {
176: if (newCount <= limit)
177: setCount(nr);
178: }
179: });
180: }
181: return ov.head[ovNr];
182: }
183:
184: public boolean unifyOuterValues(int nr, Expression otherExpr) {
185: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
186: GlobalOptions.err.println("unifyOuterValues: " + this + ","
187: + nr + "," + otherExpr);
188: /** require nr < getCount() **/
189: Expression expr1 = otherExpr;
190: Expression expr2 = head[nr];
191: LocalInfo li1;
192:
193: /* Wow, unifying outer values of different constructors in
194: * different methods of different classes can get complicated.
195: * We have not committed the number of OuterValues. So we
196: * can't say for sure, if the local load matches an outer
197: * local if this is a constructor. Even worse: The previous
198: * outerValues may be a load of a constructor local, that
199: * should be used as outer value...
200: *
201: * See MethodScopeTest for examples.
202: *
203: * We look if there is a way to merge them and register an
204: * outer value listener to lots of classes.
205: */
206:
207: if (expr1 instanceof ThisOperator) {
208: li1 = null;
209: } else if (expr1 instanceof OuterLocalOperator) {
210: li1 = ((OuterLocalOperator) expr1).getLocalInfo();
211: } else if (expr1 instanceof LocalLoadOperator) {
212: li1 = ((LocalLoadOperator) expr1).getLocalInfo();
213: } else
214: return false;
215:
216: /* First lift expr1 until it is a parent of this class */
217: while (li1 != null
218: && !li1.getMethodAnalyzer().isMoreOuterThan(
219: clazzAnalyzer)) {
220: expr1 = liftOuterValue(li1, nr);
221: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
222: GlobalOptions.err.println(" lift1 " + li1 + " in "
223: + li1.getMethodAnalyzer() + " to " + expr1);
224:
225: if (expr1 instanceof ThisOperator) {
226: li1 = null;
227: } else if (expr1 instanceof OuterLocalOperator) {
228: li1 = ((OuterLocalOperator) expr1).getLocalInfo();
229: } else
230: return false;
231: }
232: /* Now lift expr2 until expr1 and expr2 are equal */
233: while (!expr1.equals(expr2)) {
234: if (expr2 instanceof OuterLocalOperator) {
235: LocalInfo li2 = ((OuterLocalOperator) expr2)
236: .getLocalInfo();
237:
238: /* if expr1 and expr2 point to same local, we have
239: * succeeded (note that expr1 may be an LocalLoadOperator)
240: */
241: if (li2.equals(li1))
242: break;
243:
244: expr2 = liftOuterValue(li2, nr);
245: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
246: GlobalOptions.err
247: .println(" lift2 " + li2 + " in "
248: + li2.getMethodAnalyzer() + " to "
249: + expr2);
250:
251: } else
252: return false;
253: }
254:
255: if ((GlobalOptions.debuggingFlags & GlobalOptions.DEBUG_CONSTRS) != 0)
256: GlobalOptions.err.println("unifyOuterValues succeeded.");
257: return true;
258: }
259:
260: /**
261: * Jikes gives the outer class reference in an unusual place (as last
262: * parameter) for anonymous classes that extends an inner (or method
263: * scope) class. This method tells if this is such a class.
264: */
265: public boolean isJikesAnonymousInner() {
266: return jikesAnonymousInner;
267: }
268:
269: /**
270: * Javac 1.3 doesn't give an outer class reference for anonymous
271: * classes that extend inner classes, provided the outer class is
272: * the normal this parameter. Instead it takes a normal outer
273: * value parameter for this. This method tells if this is such a
274: * class.
275: */
276: public boolean isImplicitOuterClass() {
277: return implicitOuterClass;
278: }
279:
280: public void addOuterValueListener(OuterValueListener l) {
281: if (ovListeners == null)
282: ovListeners = new Vector();
283: ovListeners.addElement(l);
284: }
285:
286: /**
287: * Jikes gives the outer class reference in an unusual place (as last
288: * parameter) for anonymous classes that extends an inner (or method
289: * scope) class. This method tells if this is such a class.
290: */
291: public void setJikesAnonymousInner(boolean value) {
292: jikesAnonymousInner = value;
293: }
294:
295: public void setImplicitOuterClass(boolean value) {
296: implicitOuterClass = value;
297: }
298:
299: private static int countSlots(Expression[] exprs, int length) {
300: int slots = 0;
301: for (int i = 0; i < length; i++)
302: slots += exprs[i].getType().stackSize();
303: return slots;
304: }
305:
306: public void setMinCount(int newMin) {
307: if (headCount < newMin) {
308: GlobalOptions.err
309: .println("WARNING: something got wrong with scoped class "
310: + clazzAnalyzer.getClazz()
311: + ": "
312: + newMin
313: + "," + headCount);
314: new Throwable().printStackTrace(GlobalOptions.err);
315: headMinCount = headCount;
316: } else if (newMin > headMinCount)
317: headMinCount = newMin;
318: }
319:
320: public void setCount(int newHeadCount) {
321: if (newHeadCount >= headCount)
322: return;
323: headCount = newHeadCount;
324:
325: if ((GlobalOptions.debuggingFlags
326: & GlobalOptions.DEBUG_CONSTRS) != 0) {
327: GlobalOptions.err.println("setCount: "+this +","+newHeadCount);
328: new Throwable().printStackTrace(GlobalOptions.err);
329: }
330:
331: if (newHeadCount < headMinCount) {
332: GlobalOptions.err.println
333: ("WARNING: something got wrong with scoped class "
334: +clazzAnalyzer.getClazz()+": "
335: +headMinCount+","+headCount);
336: new Throwable().printStackTrace(GlobalOptions.err);
337: headMinCount = newHeadCount;
338: }
339:
340: if (ovListeners != null) {
341: for (Enumeration enum = ovListeners.elements();
342: enum.hasMoreElements();)
343: ((OuterValueListener) enum.nextElement()
344: ).shrinkingOuterValues(this , newHeadCount);
345: }
346: }
347:
348: public String toString() {
349: StringBuffer sb = new StringBuffer().append(
350: clazzAnalyzer.getClazz()).append(".OuterValues[");
351: String comma = "";
352: int slot = 1;
353: for (int i = 0; i < headCount; i++) {
354: if (i == headMinCount)
355: sb.append("<-");
356: sb.append(comma).append(slot).append(":").append(head[i]);
357: slot += head[i].getType().stackSize();
358: comma = ",";
359: }
360: if (jikesAnonymousInner)
361: sb.append("!jikesAnonymousInner");
362: if (implicitOuterClass)
363: sb.append("!implicitOuterClass");
364: return sb.append("]").toString();
365: }
366: }
|