You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1134 lines
35 KiB
1134 lines
35 KiB
/* TransformConstructors 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.flow;
|
|
import java.lang.reflect.Modifier;
|
|
import jode.GlobalOptions;
|
|
import jode.Decompiler;
|
|
import jode.decompiler.Analyzer;
|
|
import jode.decompiler.ClassAnalyzer;
|
|
import jode.decompiler.MethodAnalyzer;
|
|
import jode.decompiler.FieldAnalyzer;
|
|
import jode.decompiler.MethodAnalyzer;
|
|
import jode.decompiler.OuterValueListener;
|
|
import jode.expr.*;
|
|
import jode.type.MethodType;
|
|
import jode.type.Type;
|
|
import jode.bytecode.ClassInfo;
|
|
import jode.bytecode.InnerClassInfo;
|
|
|
|
import java.util.Vector;
|
|
import java.util.Enumeration;
|
|
|
|
/**
|
|
* This class will transform the constructors. This involves several
|
|
* steps:
|
|
*
|
|
* <ul><li>remove implicit super() call</li>
|
|
* <li>move constant field initializations that occur in all constructors
|
|
* (except those that start with a this() call) to the fields.</li>
|
|
* <li>For inner classes check if the this$0 field(s) is/are
|
|
* initialized corectly, remove the initializer and mark that
|
|
* field. </li>
|
|
* <li>For method scope classes also check the val$xx fields.</li>
|
|
* <li>For jikes class check for a constructor$xx call, and mark that
|
|
* as the real constructor, moving the super call of the original
|
|
* constructor</li>
|
|
* <li>For anonymous classes check that the constructor only contains
|
|
* a super call and mark it as default</li></ul>
|
|
*
|
|
* It will make use of the <code>outerValues</code> expression, that
|
|
* tell which parameters (this and final method variables) are always
|
|
* given to the constructor.
|
|
*
|
|
* You can debug this class with the <code>--debug=constructors</code>
|
|
* switch.
|
|
*
|
|
* @author Jochen Hoenicke
|
|
* @see jode.decompiler.FieldAnalyzer#setInitializer
|
|
* @see jode.decompiler.ClassAnalyzer#getOuterValues
|
|
*/
|
|
public class TransformConstructors implements OuterValueListener {
|
|
/* What is sometimes confusing is the distinction between slot and
|
|
* parameter. Most times parameter nr = slot nr, but double and
|
|
* long parameters take two slots, so the remaining parameters
|
|
* will shift.
|
|
*/
|
|
|
|
ClassAnalyzer clazzAnalyzer;
|
|
boolean isStatic;
|
|
MethodAnalyzer[] cons;
|
|
|
|
/**
|
|
* @see jode.decompiler.ClassAnalyzer#outerValues
|
|
*/
|
|
Expression[] outerValues;
|
|
/**
|
|
* The minimal first slot number after the outerValues. This is because
|
|
* the outerValues array may shrink to the desired size. */
|
|
int ovMinSlots;
|
|
/**
|
|
* The maximal first slot number after the outerValues.
|
|
*/
|
|
int ovMaxSlots;
|
|
|
|
boolean jikesAnonInner = false;
|
|
|
|
public TransformConstructors(ClassAnalyzer clazzAnalyzer,
|
|
boolean isStatic, MethodAnalyzer[] cons) {
|
|
this.clazzAnalyzer = clazzAnalyzer;
|
|
this.isStatic = isStatic;
|
|
this.cons = cons;
|
|
this.ovMinSlots = 1;
|
|
this.outerValues = clazzAnalyzer.getOuterValues();
|
|
if (outerValues != null) {
|
|
clazzAnalyzer.addOuterValueListener(this);
|
|
ovMaxSlots = Integer.MAX_VALUE;
|
|
updateOuterValues(outerValues.length);
|
|
} else
|
|
ovMaxSlots = 1;
|
|
}
|
|
|
|
public String dumpOuterValues() {
|
|
if (outerValues == null)
|
|
return "null";
|
|
StringBuffer sb = new StringBuffer();
|
|
int slot = 1;
|
|
for (int i=0; i < outerValues.length; i++) {
|
|
if (i>0)
|
|
sb.append(", ");
|
|
if (slot == ovMinSlots)
|
|
sb.append("[");
|
|
sb.append(outerValues[i]);
|
|
if (slot == ovMaxSlots)
|
|
sb.append("]");
|
|
slot += outerValues[i].getType().stackSize();
|
|
}
|
|
sb.append(" ["+ovMinSlots+","+ovMaxSlots+"]");
|
|
return sb.toString();
|
|
}
|
|
|
|
public void updateOuterValues(int count) {
|
|
int outerSlots = 1;
|
|
outerValues = clazzAnalyzer.getOuterValues();
|
|
for (int i=0; i< count; i++)
|
|
outerSlots += outerValues[i].getType().stackSize();
|
|
if (outerSlots < ovMaxSlots)
|
|
ovMaxSlots = outerSlots;
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("OuterValues: " + dumpOuterValues());
|
|
|
|
if (ovMaxSlots < ovMinSlots) {
|
|
GlobalOptions.err.println
|
|
("WARNING: something got wrong with scoped class "
|
|
+clazzAnalyzer.getClazz()+": "+ovMinSlots+","+ovMaxSlots);
|
|
GlobalOptions.err.println
|
|
("CAN'T REPAIR. PRODUCED CODE IS PROBABLY WRONG.");
|
|
new Throwable().printStackTrace(GlobalOptions.err);
|
|
ovMinSlots = outerSlots;
|
|
}
|
|
}
|
|
|
|
public void shrinkingOuterValues(ClassAnalyzer ca, int newCount) {
|
|
updateOuterValues(newCount);
|
|
}
|
|
|
|
public static boolean isThis(Expression thisExpr, ClassInfo clazz) {
|
|
return ((thisExpr instanceof ThisOperator)
|
|
&& (((ThisOperator)thisExpr).getClassInfo() == clazz));
|
|
}
|
|
|
|
/**
|
|
* Translate a slot into an index into the outerValues array.
|
|
* @return index into outerValues array or -1, if not matched.
|
|
*/
|
|
public int getOuterValueIndex(int slot) {
|
|
int ovSlot = 1; // slot of first outerValue
|
|
for (int i=0; i < outerValues.length; i++) {
|
|
if (ovSlot == slot)
|
|
return i;
|
|
ovSlot += outerValues[i].getType().stackSize();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* This will be
|
|
*/
|
|
private boolean checkAnonymousConstructor(MethodAnalyzer constr,
|
|
InstructionBlock superBlock) {
|
|
|
|
if (clazzAnalyzer.getName() != null)
|
|
return false;
|
|
|
|
/**
|
|
* Situation:
|
|
* constructor(outerValues, params) {
|
|
* super(params);
|
|
* }
|
|
*
|
|
* Mark constructor as anonymous constructor.
|
|
*/
|
|
Expression expr = superBlock.getInstruction().simplify();
|
|
|
|
if (!(expr instanceof InvokeOperator))
|
|
return false;
|
|
|
|
InvokeOperator superCall = (InvokeOperator) expr;
|
|
superBlock.setInstruction(superCall);
|
|
|
|
if (superCall.getFreeOperandCount() != 0
|
|
|| !superCall.isConstructor() || !superCall.isSuperOrThis())
|
|
return false;
|
|
|
|
Expression[] subExpr = superCall.getSubExpressions();
|
|
Expression thisExpr = subExpr[0];
|
|
if (!isThis(thisExpr, clazzAnalyzer.getClazz()))
|
|
return false;
|
|
|
|
Type[] constrParams = constr.getType().getParameterTypes();
|
|
|
|
int ovLength = constrParams.length - (subExpr.length - 1);
|
|
int ovSlots = 1;
|
|
for (int i=0; i< ovLength; i++) {
|
|
ovSlots += outerValues[i].getType().stackSize();
|
|
}
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("anonymousConstructor: slots expected: "
|
|
+ovSlots+" possible: ["
|
|
+ovMinSlots+","+ovMaxSlots+"]");
|
|
if (ovSlots < ovMinSlots || ovSlots > ovMaxSlots)
|
|
return false;
|
|
int slot = ovSlots;
|
|
int start = jikesAnonInner ? 2 : 1;
|
|
for (int i = start; i < subExpr.length; i++) {
|
|
if (!(subExpr[i] instanceof LocalLoadOperator))
|
|
return false;
|
|
LocalLoadOperator llop = (LocalLoadOperator) subExpr[i];
|
|
|
|
if (llop.getLocalInfo().getSlot() != slot)
|
|
return false;
|
|
slot += subExpr[i].getType().stackSize();
|
|
}
|
|
if (jikesAnonInner) {
|
|
if (!(subExpr[1] instanceof LocalLoadOperator))
|
|
return false;
|
|
LocalLoadOperator llop = (LocalLoadOperator) subExpr[1];
|
|
|
|
if (llop.getLocalInfo().getSlot() != slot)
|
|
return false;
|
|
}
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("anonymousConstructors succeeded");
|
|
ovMinSlots = ovMaxSlots = ovSlots;
|
|
constr.setAnonymousConstructor(true);
|
|
return true;
|
|
}
|
|
|
|
public boolean checkJikesSuperAndFillLoads(Expression expr,
|
|
Vector localLoads) {
|
|
if (expr instanceof LocalStoreOperator
|
|
|| expr instanceof IIncOperator)
|
|
return false;
|
|
|
|
if (expr instanceof LocalLoadOperator) {
|
|
LocalLoadOperator llop = (LocalLoadOperator) expr;
|
|
int slot = llop.getLocalInfo().getSlot();
|
|
if (slot != 1) {
|
|
/* slot 1 is outerValues[0], which is okay */
|
|
if (slot < ovMinSlots)
|
|
return false;
|
|
if (slot < ovMaxSlots)
|
|
ovMaxSlots = slot;
|
|
}
|
|
localLoads.addElement(llop);
|
|
}
|
|
if (expr instanceof Operator) {
|
|
Expression subExpr[] = ((Operator)expr).getSubExpressions();
|
|
for (int i=0; i< subExpr.length; i++) {
|
|
if (!checkJikesSuperAndFillLoads(subExpr[i], localLoads))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public boolean checkJikesContinuation(MethodAnalyzer constr) {
|
|
MethodType constrType = constr.getType();
|
|
|
|
/*
|
|
* Situation:
|
|
* constructor(outerValues, params) {
|
|
* [optional: super(expressions builded of (outerValues[0], params))]
|
|
* constructor$?(outerValues[0], params);
|
|
* }
|
|
*
|
|
* For anonymous classes that extends class/method scope classes
|
|
* the situation is more unusal:
|
|
* constructor(outerValues, params, outerClass) {
|
|
* outerClass.super(params);
|
|
* constructor$?(outerValues[0], params);
|
|
* }
|
|
*
|
|
* The outerValues[0] parameter is the normal this of the
|
|
* surrounding method (but what is the surrounding method?
|
|
* That can't be determined in some cases). If the
|
|
s * surrounding method is static, the outerValues[0] parameter
|
|
* disappears!
|
|
*
|
|
* Move optional super to method constructor$?
|
|
* (renaming local variables) and mark constructor and
|
|
* constructor$? as Jikes constructor. */
|
|
|
|
StructuredBlock sb = constr.getMethodHeader().block;
|
|
|
|
Vector localLoads = null;
|
|
InstructionBlock superBlock = null;
|
|
boolean mayBeAnonInner = false;
|
|
int anonInnerSlot = 1;
|
|
if (sb instanceof SequentialBlock) {
|
|
|
|
if (!(sb.getSubBlocks()[0] instanceof InstructionBlock)
|
|
|| !(sb.getSubBlocks()[1] instanceof InstructionBlock))
|
|
return false;
|
|
superBlock = (InstructionBlock) sb.getSubBlocks()[0];
|
|
sb = sb.getSubBlocks()[1];
|
|
|
|
Expression superExpr = superBlock.getInstruction().simplify();
|
|
if (!(superExpr instanceof InvokeOperator))
|
|
return false;
|
|
|
|
InvokeOperator superInvoke = (InvokeOperator) superExpr;
|
|
superBlock.setInstruction(superInvoke);
|
|
|
|
if (superInvoke.getFreeOperandCount() != 0
|
|
|| !superInvoke.isConstructor()
|
|
|| !superInvoke.isSuperOrThis())
|
|
return false;
|
|
|
|
Expression[] subExpr = superInvoke.getSubExpressions();
|
|
Expression thisExpr = subExpr[0];
|
|
if (!isThis(thisExpr, clazzAnalyzer.getClazz()))
|
|
return false;
|
|
|
|
ClassInfo superClazz = superInvoke.getClassInfo();
|
|
if ((Decompiler.options &
|
|
(Decompiler.OPTION_ANON | Decompiler.OPTION_INNER)) != 0
|
|
&& clazzAnalyzer.getName() == null
|
|
&& superClazz.getOuterClasses() != null
|
|
&& subExpr[1] instanceof LocalLoadOperator) {
|
|
Type[] paramTypes = constrType.getParameterTypes();
|
|
anonInnerSlot = 1;
|
|
for (int i=0; i< paramTypes.length-1; i++) {
|
|
anonInnerSlot += paramTypes[i].stackSize();
|
|
}
|
|
if (((LocalLoadOperator)
|
|
subExpr[1]).getLocalInfo().getSlot() == anonInnerSlot)
|
|
mayBeAnonInner = true;
|
|
}
|
|
|
|
localLoads = new Vector();
|
|
for (int i=2; i< subExpr.length; i++) {
|
|
if (!checkJikesSuperAndFillLoads(subExpr[i], localLoads))
|
|
return false;
|
|
}
|
|
|
|
} else if (!(sb instanceof InstructionBlock))
|
|
return false;
|
|
|
|
/* Now check the constructor$? invocation */
|
|
Expression lastExpr
|
|
= ((InstructionBlock)sb).getInstruction().simplify();
|
|
if (!(lastExpr instanceof InvokeOperator))
|
|
return false;
|
|
|
|
InvokeOperator invoke = (InvokeOperator) lastExpr;
|
|
if (!invoke.isThis()
|
|
|| invoke.getFreeOperandCount() != 0)
|
|
return false;
|
|
MethodAnalyzer methodAna = invoke.getMethodAnalyzer();
|
|
if (methodAna == null)
|
|
return false;
|
|
MethodType methodType = methodAna.getType();
|
|
Expression[] constrParams = invoke.getSubExpressions();
|
|
|
|
if (!methodAna.getName().startsWith("constructor$")
|
|
|| methodType.getReturnType() != Type.tVoid)
|
|
return false;
|
|
|
|
if (!isThis(invoke.getSubExpressions()[0],
|
|
clazzAnalyzer.getClazz()))
|
|
return false;
|
|
|
|
/* Now check if this must be of the anonymous extends inner
|
|
* class form */
|
|
if (mayBeAnonInner
|
|
&& constrParams.length > 0
|
|
&& !(constrParams[constrParams.length-1]
|
|
instanceof LocalLoadOperator
|
|
&& (((LocalLoadOperator)
|
|
constrParams[constrParams.length-1]).getLocalInfo()
|
|
.getSlot() == anonInnerSlot)))
|
|
jikesAnonInner = true;
|
|
|
|
|
|
int ovLength = constrType.getParameterTypes().length
|
|
- methodType.getParameterTypes().length;
|
|
|
|
if (jikesAnonInner)
|
|
ovLength--;
|
|
|
|
int firstArg = 1;
|
|
boolean unsureOuter = false;
|
|
boolean canHaveOuter = false;
|
|
if (outerValues != null && outerValues.length > 0
|
|
&& outerValues[0] instanceof ThisOperator
|
|
&& firstArg < constrParams.length
|
|
&& constrParams[firstArg] instanceof LocalLoadOperator
|
|
&& ((LocalLoadOperator)
|
|
constrParams[firstArg]).getLocalInfo().getSlot() == 1) {
|
|
if (ovLength == 0 && ovMinSlots == 1)
|
|
/* It is not sure, if outerValues[0] is present or not.
|
|
*/
|
|
unsureOuter = true;
|
|
|
|
/* Assume outerValues[0] is there */
|
|
ovLength++;
|
|
firstArg++;
|
|
canHaveOuter = true;
|
|
}
|
|
|
|
if (ovLength > outerValues.length)
|
|
return false;
|
|
int ovSlots = 1;
|
|
for (int i=0; i< ovLength; i++) {
|
|
ovSlots += outerValues[i].getType().stackSize();
|
|
}
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("jikesConstrCont: slots expected: "
|
|
+ovSlots+" possible: ["
|
|
+ovMinSlots+","+ovMaxSlots+"]");
|
|
|
|
if (ovSlots < ovMinSlots || ovSlots > ovMaxSlots)
|
|
return false;
|
|
|
|
{
|
|
int slot = ovSlots;
|
|
for (int j = firstArg; j < constrParams.length; j++) {
|
|
if (!(constrParams[j] instanceof LocalLoadOperator))
|
|
return false;
|
|
LocalLoadOperator llop
|
|
= (LocalLoadOperator) constrParams[j];
|
|
if (llop.getLocalInfo().getSlot() != slot)
|
|
return false;
|
|
slot += constrParams[j].getType().stackSize();
|
|
}
|
|
}
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("jikesConstrCont succeded.");
|
|
|
|
if (unsureOuter) {
|
|
ovMaxSlots = 2;
|
|
} else {
|
|
ovMinSlots = ovMaxSlots = ovSlots;
|
|
}
|
|
if (superBlock != null
|
|
&& checkAnonymousConstructor(constr, superBlock)) {
|
|
superBlock = null;
|
|
}
|
|
|
|
constr.getMethodHeader().block.removeBlock();
|
|
|
|
/* Now move the constructor call.
|
|
*/
|
|
if (superBlock != null) {
|
|
SequentialBlock sequBlock = new SequentialBlock();
|
|
Enumeration enum = localLoads.elements();
|
|
while (enum.hasMoreElements()) {
|
|
LocalLoadOperator llop
|
|
= (LocalLoadOperator) enum.nextElement();
|
|
int slot = llop.getLocalInfo().getSlot();
|
|
|
|
int newSlot = (slot == 1
|
|
? 1 /* outerValues[0] */
|
|
: slot - ovSlots + 2);
|
|
llop.setMethodAnalyzer(methodAna);
|
|
llop.setLocalInfo(methodAna.getLocalInfo(0, newSlot));
|
|
}
|
|
methodAna.insertStructuredBlock(superBlock);
|
|
}
|
|
clazzAnalyzer.setJikesAnonymousInner(jikesAnonInner);
|
|
constr.setJikesConstructor(true);
|
|
methodAna.setJikesConstructor(true);
|
|
methodAna.setHasOuterValue(canHaveOuter);
|
|
if (constr.isAnonymousConstructor()
|
|
&& methodAna.getMethodHeader().block instanceof EmptyBlock)
|
|
methodAna.setAnonymousConstructor(true);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This methods checks if expr is a valid field initializer. It
|
|
* will also merge outerValues, that occur in expr.
|
|
* @param expr the initializer to check
|
|
* @return the transformed initializer or null if expr is not valid.
|
|
*/
|
|
public Expression transformFieldInitializer(Expression expr) {
|
|
if (expr instanceof LocalVarOperator) {
|
|
if (!(expr instanceof LocalLoadOperator)) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("illegal local op: "+expr);
|
|
return null;
|
|
}
|
|
if (outerValues != null
|
|
&& (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0) {
|
|
int slot = ((LocalLoadOperator)expr).getLocalInfo().getSlot();
|
|
int pos = getOuterValueIndex(slot);
|
|
if (pos >= 0 && slot < ovMaxSlots) {
|
|
expr = outerValues[pos];
|
|
if (slot >= ovMinSlots)
|
|
ovMinSlots = slot + expr.getType().stackSize();
|
|
return expr;
|
|
}
|
|
}
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("not outerValue: "+expr
|
|
+" ["+ovMinSlots
|
|
+","+ovMaxSlots+"]");
|
|
return null;
|
|
}
|
|
if (expr instanceof Operator) {
|
|
Operator op = (Operator) expr;
|
|
Expression[] subExpr = op.getSubExpressions();
|
|
for (int i=0; i< subExpr.length; i++) {
|
|
Expression transformed = transformFieldInitializer(subExpr[i]);
|
|
if (transformed == null)
|
|
return null;
|
|
if (transformed != subExpr[i])
|
|
op.setSubExpressions(i, transformed);
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
public void initSyntheticFields() {
|
|
if ((Decompiler.options & Decompiler.OPTION_CONTRAFO) == 0)
|
|
return;
|
|
if (isStatic)
|
|
return;
|
|
|
|
if (cons.length == 0)
|
|
return;
|
|
|
|
int constrCount = cons.length;
|
|
StructuredBlock[] sb = new StructuredBlock[constrCount];
|
|
for (int i=0; i< constrCount; ) {
|
|
FlowBlock header = cons[i].getMethodHeader();
|
|
/* Check that code block is fully analyzed */
|
|
if (header == null || !header.hasNoJumps())
|
|
return;
|
|
|
|
/* sb[i] will iterate the instructions of the constructor. */
|
|
sb[i] = cons[i].getMethodHeader().block;
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+": "+sb[i]);
|
|
|
|
/* A non static constructor must begin with a call to
|
|
* another constructor. Either to a constructor of the
|
|
* same class or to the super class
|
|
*/
|
|
InstructionBlock ib;
|
|
if (sb[i] instanceof InstructionBlock)
|
|
ib = (InstructionBlock)sb[i];
|
|
else if (sb[i] instanceof SequentialBlock
|
|
&& (sb[i].getSubBlocks()[0]
|
|
instanceof InstructionBlock))
|
|
ib = (InstructionBlock) sb[i].getSubBlocks()[0];
|
|
else
|
|
return;
|
|
|
|
Expression superExpr = ib.getInstruction().simplify();
|
|
if (!(superExpr instanceof InvokeOperator)
|
|
|| superExpr.getFreeOperandCount() != 0)
|
|
return;
|
|
InvokeOperator superInvoke = (InvokeOperator) superExpr;
|
|
if (!superInvoke.isConstructor()
|
|
|| !superInvoke.isSuperOrThis())
|
|
return;
|
|
Expression thisExpr = superInvoke.getSubExpressions()[0];
|
|
if (!isThis(thisExpr, clazzAnalyzer.getClazz()))
|
|
return;
|
|
|
|
if (superInvoke.isThis()) {
|
|
/* This constructor calls another constructor of this
|
|
* class, which will do the initialization. We can skip
|
|
* this constructor.
|
|
*/
|
|
/* Move constructor to the end of cons array, and
|
|
* decrease constrCount. It will not be transformed
|
|
* any further.
|
|
*/
|
|
MethodAnalyzer temp = cons[i];
|
|
cons[i] = cons[--constrCount];
|
|
cons[constrCount] = temp;
|
|
continue;
|
|
}
|
|
|
|
/* This constructor begins with a super call, as
|
|
* expected.
|
|
*/
|
|
|
|
if (sb[i] instanceof SequentialBlock)
|
|
sb[i] = sb[i].getSubBlocks()[1];
|
|
else
|
|
sb[i] = null;
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("normal constructor");
|
|
i++;
|
|
}
|
|
StructuredBlock[] start = new StructuredBlock[constrCount];
|
|
for (int i=0; i< constrCount; i++)
|
|
start[i] = sb[i];
|
|
|
|
big_loop:
|
|
for (;;) {
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (sb[i] == null) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+" is over");
|
|
break big_loop;
|
|
}
|
|
}
|
|
|
|
StructuredBlock ib =
|
|
(sb[0] instanceof SequentialBlock)
|
|
? sb[0].getSubBlocks()[0]
|
|
: sb[0];
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("fieldInit: "+ib);
|
|
|
|
if (!(ib instanceof InstructionBlock))
|
|
break big_loop;
|
|
|
|
Expression instr
|
|
= ((InstructionBlock) ib).getInstruction().simplify();
|
|
|
|
if (!(instr instanceof StoreInstruction)
|
|
|| instr.getFreeOperandCount() != 0)
|
|
break big_loop;
|
|
|
|
StoreInstruction store = (StoreInstruction) instr;
|
|
if (!(store.getLValue() instanceof PutFieldOperator))
|
|
break big_loop;
|
|
|
|
PutFieldOperator pfo = (PutFieldOperator) store.getLValue();
|
|
if (pfo.isStatic() != isStatic || !pfo.isThis())
|
|
break big_loop;
|
|
|
|
if (!isThis(pfo.getSubExpressions()[0],
|
|
clazzAnalyzer.getClazz()))
|
|
break big_loop;
|
|
|
|
FieldAnalyzer field = clazzAnalyzer.getField(pfo.getFieldName(),
|
|
pfo.getFieldType());
|
|
|
|
/* Don't check for final. Jikes sometimes omits this attribute.
|
|
*/
|
|
if (!field.isSynthetic())
|
|
break big_loop;
|
|
|
|
Expression expr = store.getSubExpressions()[1];
|
|
expr = transformFieldInitializer(expr);
|
|
if (expr == null)
|
|
break big_loop;
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("field " + pfo.getFieldName()
|
|
+ " = " + expr);
|
|
|
|
for (int i=1; i< constrCount; i++) {
|
|
ib = (sb[i] instanceof SequentialBlock)
|
|
? sb[i].getSubBlocks()[0]
|
|
: sb[i];
|
|
if (!(ib instanceof InstructionBlock)
|
|
|| !(((InstructionBlock)ib).getInstruction().simplify()
|
|
.equals(instr))) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+" differs: "+ib);
|
|
break big_loop;
|
|
}
|
|
}
|
|
|
|
|
|
if (!(field.setInitializer(expr))) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("setField failed");
|
|
break big_loop;
|
|
}
|
|
|
|
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (sb[i] instanceof SequentialBlock)
|
|
sb[i] = sb[i].getSubBlocks()[1];
|
|
else
|
|
sb[i] = null;
|
|
}
|
|
}
|
|
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (start[i] != null) {
|
|
if (sb[i] == null)
|
|
start[i].removeBlock();
|
|
else {
|
|
sb[i].replace(start[i]);
|
|
sb[i].simplify();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void transformBlockInitializer(StructuredBlock block) {
|
|
StructuredBlock start = block;
|
|
while (block != null) {
|
|
init_loop:
|
|
do {
|
|
StructuredBlock ib =
|
|
(block instanceof SequentialBlock)
|
|
? block.getSubBlocks()[0]
|
|
: block;
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("fieldInit: "+ib);
|
|
|
|
if (!(ib instanceof InstructionBlock))
|
|
break init_loop;
|
|
|
|
Expression instr
|
|
= ((InstructionBlock) ib).getInstruction().simplify();
|
|
|
|
if (!(instr instanceof StoreInstruction)
|
|
|| instr.getFreeOperandCount() != 0)
|
|
break init_loop;
|
|
|
|
StoreInstruction store = (StoreInstruction) instr;
|
|
if (!(store.getLValue() instanceof PutFieldOperator))
|
|
break init_loop;
|
|
|
|
PutFieldOperator pfo = (PutFieldOperator) store.getLValue();
|
|
if (pfo.isStatic() != isStatic || !pfo.isThis())
|
|
break init_loop;
|
|
|
|
if (!isStatic) {
|
|
if (!isThis(pfo.getSubExpressions()[0],
|
|
clazzAnalyzer.getClazz())) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("not this: "+instr);
|
|
break init_loop;
|
|
}
|
|
}
|
|
|
|
Expression expr = store.getSubExpressions()[1];
|
|
expr = transformFieldInitializer(expr);
|
|
if (expr == null)
|
|
break init_loop;
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("field " + pfo.getFieldName()
|
|
+ " = " + expr);
|
|
|
|
FieldAnalyzer field = clazzAnalyzer
|
|
.getField(pfo.getFieldName(), pfo.getFieldType());
|
|
|
|
if (!(field.setInitializer(expr))) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("setField failed");
|
|
break init_loop;
|
|
}
|
|
|
|
block.removeBlock();
|
|
if (start != block) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("adding block initializer");
|
|
|
|
clazzAnalyzer.addBlockInitializer(field, start);
|
|
}
|
|
|
|
if (block instanceof SequentialBlock)
|
|
start = block.getSubBlocks()[1];
|
|
else
|
|
start = null;
|
|
} while (false);
|
|
|
|
if (block instanceof SequentialBlock)
|
|
block = block.getSubBlocks()[1];
|
|
else
|
|
block = null;
|
|
}
|
|
|
|
if (start != null) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("adding block initializer");
|
|
|
|
clazzAnalyzer.addBlockInitializer(null, start);
|
|
}
|
|
}
|
|
|
|
public boolean checkBlockInitializer(InvokeOperator invoke) {
|
|
if (!invoke.isThis()
|
|
|| invoke.getFreeOperandCount() != 0)
|
|
return false;
|
|
MethodAnalyzer methodAna = invoke.getMethodAnalyzer();
|
|
if (methodAna == null)
|
|
return false;
|
|
FlowBlock flow = methodAna.getMethodHeader();
|
|
MethodType methodType = methodAna.getType();
|
|
if (!methodAna.getName().startsWith("block$")
|
|
|| methodType.getParameterTypes().length != 0
|
|
|| methodType.getReturnType() != Type.tVoid)
|
|
return false;
|
|
if (flow == null || !flow.hasNoJumps())
|
|
return false;
|
|
|
|
if (!isThis(invoke.getSubExpressions()[0],
|
|
clazzAnalyzer.getClazz()))
|
|
return false;
|
|
|
|
methodAna.setJikesBlockInitializer(true);
|
|
transformBlockInitializer(flow.block);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* This does the normal constructor transformations.
|
|
*
|
|
* javac copies the field initializers to each constructor. This
|
|
* will undo the transformation: it will tell the fields about the
|
|
* initial value and removes the initialization from all constructors.
|
|
*
|
|
* There are of course many checks necessary: All field
|
|
* initializers must be equal in all constructors, and there
|
|
* mustn't be locals that used in field initialization (except
|
|
* outerValue - locals).
|
|
*/
|
|
public void transform() {
|
|
if (cons.length == 0)
|
|
return;
|
|
|
|
int constrCount = cons.length;
|
|
StructuredBlock[] sb = new StructuredBlock[constrCount];
|
|
for (int i=0; i< constrCount; ) {
|
|
FlowBlock header = cons[i].getMethodHeader();
|
|
/* Check that code block is fully analyzed */
|
|
if (header == null || !header.hasNoJumps())
|
|
return;
|
|
|
|
/* sb[i] will iterate the instructions of the constructor. */
|
|
sb[i] = cons[i].getMethodHeader().block;
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+": "+sb[i]);
|
|
|
|
if (!isStatic) {
|
|
/* A non static constructor must begin with a call to
|
|
* another constructor. Either to a constructor of the
|
|
* same class or to the super class
|
|
*/
|
|
InstructionBlock ib;
|
|
if (sb[i] instanceof InstructionBlock)
|
|
ib = (InstructionBlock)sb[i];
|
|
else if (sb[i] instanceof SequentialBlock
|
|
&& (sb[i].getSubBlocks()[0]
|
|
instanceof InstructionBlock))
|
|
ib = (InstructionBlock) sb[i].getSubBlocks()[0];
|
|
else
|
|
return;
|
|
|
|
Expression superExpr = ib.getInstruction().simplify();
|
|
if (!(superExpr instanceof InvokeOperator)
|
|
|| superExpr.getFreeOperandCount() != 0)
|
|
return;
|
|
InvokeOperator superInvoke = (InvokeOperator) superExpr;
|
|
if (!superInvoke.isConstructor()
|
|
|| !superInvoke.isSuperOrThis())
|
|
return;
|
|
Expression thisExpr = superInvoke.getSubExpressions()[0];
|
|
if (!isThis(thisExpr, clazzAnalyzer.getClazz()))
|
|
return;
|
|
|
|
if (superInvoke.isThis()) {
|
|
/* This constructor calls another constructor of this
|
|
* class, which will do the initialization. We can skip
|
|
* this constructor.
|
|
*/
|
|
/* But first check that outerValues are correctly promoted
|
|
* XXX: Note that I couldn't check this code, yet,
|
|
* since I couldn't find a compiler that can handle
|
|
* this() calls in method scoped classes :-(
|
|
*/
|
|
for (int slot = 1, j=0; slot < ovMaxSlots; j++) {
|
|
Expression param
|
|
= superInvoke.getSubExpressions()[j+1];
|
|
if (!(param instanceof LocalLoadOperator)
|
|
|| ((LocalLoadOperator)
|
|
param).getLocalInfo().getSlot() != slot) {
|
|
ovMaxSlots = slot;
|
|
break;
|
|
}
|
|
slot += param.getType().stackSize();
|
|
}
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("skipping this()");
|
|
/* Move constructor to the end of cons array, and
|
|
* decrease constrCount. It will not be transformed
|
|
* any further.
|
|
*/
|
|
MethodAnalyzer temp = cons[i];
|
|
cons[i] = cons[--constrCount];
|
|
cons[constrCount] = temp;
|
|
continue;
|
|
}
|
|
|
|
/* This constructor begins with a super call, as
|
|
* expected.
|
|
*/
|
|
ClassInfo superClazz = superInvoke.getClassInfo();
|
|
InnerClassInfo[] outers = superClazz.getOuterClasses();
|
|
/* If the super() has no parameters (or only default
|
|
* outerValue parameter for inner/anonymous classes), we
|
|
* can remove it
|
|
*/
|
|
if ((Decompiler.options & Decompiler.OPTION_ANON) != 0
|
|
&& outers != null
|
|
&& (outers[0].outer == null || outers[0].name == null)) {
|
|
ClassAnalyzer superAnalyzer = null;
|
|
Object parent = clazzAnalyzer.getParent();
|
|
while (parent != null) {
|
|
if (parent instanceof MethodAnalyzer) {
|
|
MethodAnalyzer methodAna = (MethodAnalyzer)parent;
|
|
parent = methodAna.getClassAnalyzer().getParent();
|
|
} else
|
|
parent = ((ClassAnalyzer) parent).getParent();
|
|
}
|
|
if (superAnalyzer != null) {
|
|
/* XXX check outer values.
|
|
*/
|
|
}
|
|
} else if ((Decompiler.options & Decompiler.OPTION_INNER) != 0
|
|
&& outers != null
|
|
&& outers[0].outer != null
|
|
&& outers[0].name != null) {
|
|
if (!Modifier.isStatic(outers[0].modifiers)
|
|
&& (superInvoke.getMethodType().getParameterTypes()
|
|
.length == 1)
|
|
&& (superInvoke.getSubExpressions()[1]
|
|
instanceof ThisOperator))
|
|
ib.removeBlock();
|
|
} else if (superInvoke.getMethodType().getParameterTypes()
|
|
.length == 0)
|
|
ib.removeBlock();
|
|
|
|
if (sb[i] instanceof SequentialBlock)
|
|
sb[i] = sb[i].getSubBlocks()[1];
|
|
else
|
|
sb[i] = null;
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("normal constructor");
|
|
}
|
|
i++;
|
|
}
|
|
StructuredBlock[] start = new StructuredBlock[constrCount];
|
|
for (int i=0; i< constrCount; i++)
|
|
start[i] = sb[i];
|
|
|
|
big_loop:
|
|
for (;;) {
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (sb[i] == null) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+" is over");
|
|
break big_loop;
|
|
}
|
|
}
|
|
|
|
StructuredBlock ib =
|
|
(sb[0] instanceof SequentialBlock)
|
|
? sb[0].getSubBlocks()[0]
|
|
: sb[0];
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("fieldInit: "+ib);
|
|
|
|
if (!(ib instanceof InstructionBlock))
|
|
break big_loop;
|
|
|
|
Expression instr
|
|
= ((InstructionBlock) ib).getInstruction().simplify();
|
|
|
|
|
|
for (int i=1; i< constrCount; i++) {
|
|
ib = (sb[i] instanceof SequentialBlock)
|
|
? sb[i].getSubBlocks()[0]
|
|
: sb[i];
|
|
if (!(ib instanceof InstructionBlock)
|
|
|| !(((InstructionBlock)ib).getInstruction().simplify()
|
|
.equals(instr))) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("constr "+i+" differs: "+ib);
|
|
break big_loop;
|
|
}
|
|
}
|
|
|
|
if (instr instanceof InvokeOperator
|
|
&& checkBlockInitializer((InvokeOperator) instr)) {
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (sb[i] instanceof SequentialBlock)
|
|
sb[i] = sb[i].getSubBlocks()[1];
|
|
else
|
|
sb[i] = null;
|
|
}
|
|
break big_loop;
|
|
}
|
|
|
|
if (!(instr instanceof StoreInstruction)
|
|
|| instr.getFreeOperandCount() != 0)
|
|
break big_loop;
|
|
|
|
StoreInstruction store = (StoreInstruction) instr;
|
|
if (!(store.getLValue() instanceof PutFieldOperator))
|
|
break big_loop;
|
|
|
|
PutFieldOperator pfo = (PutFieldOperator) store.getLValue();
|
|
if (pfo.isStatic() != isStatic || !pfo.isThis())
|
|
break big_loop;
|
|
|
|
if (!isStatic) {
|
|
if (!isThis(pfo.getSubExpressions()[0],
|
|
clazzAnalyzer.getClazz())) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("not this: "+instr);
|
|
break big_loop;
|
|
}
|
|
}
|
|
|
|
Expression expr = store.getSubExpressions()[1];
|
|
expr = transformFieldInitializer(expr);
|
|
if (expr == null)
|
|
break big_loop;
|
|
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("field " + pfo.getFieldName()
|
|
+ " = " + expr);
|
|
|
|
if (!(clazzAnalyzer
|
|
.getField(pfo.getFieldName(), pfo.getFieldType())
|
|
.setInitializer(expr))) {
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("setField failed");
|
|
break big_loop;
|
|
}
|
|
|
|
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (sb[i] instanceof SequentialBlock)
|
|
sb[i] = sb[i].getSubBlocks()[1];
|
|
else
|
|
sb[i] = null;
|
|
}
|
|
}
|
|
|
|
for (int i=0; i< constrCount; i++) {
|
|
if (start[i] != null) {
|
|
if (sb[i] == null)
|
|
start[i].removeBlock();
|
|
else {
|
|
sb[i].replace(start[i]);
|
|
sb[i].simplify();
|
|
}
|
|
if ((Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0)
|
|
// Check for jikes continuation
|
|
checkJikesContinuation(cons[i]);
|
|
}
|
|
if ((Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0
|
|
&& (cons[i].getMethodHeader().block
|
|
instanceof InstructionBlock)) {
|
|
checkAnonymousConstructor(cons[i],
|
|
(InstructionBlock)
|
|
cons[i].getMethodHeader().block);
|
|
}
|
|
}
|
|
ovMinSlots = ovMaxSlots;
|
|
int ovLength = 0;
|
|
if (outerValues != null) {
|
|
for (int slot=1; slot < ovMinSlots; ) {
|
|
slot += outerValues[ovLength++].getType().stackSize();
|
|
}
|
|
if ((GlobalOptions.debuggingFlags
|
|
& GlobalOptions.DEBUG_CONSTRS) != 0)
|
|
GlobalOptions.err.println("shrinking outerValues from "
|
|
+ outerValues.length
|
|
+ " to " + ovLength);
|
|
|
|
if (ovLength < outerValues.length) {
|
|
clazzAnalyzer.shrinkOuterValues(ovLength);
|
|
}
|
|
}
|
|
|
|
/* Now tell _all_ constructors the value of outerValues-parameters
|
|
* and simplify them again.
|
|
*/
|
|
for (int i=0; i< cons.length; i++) {
|
|
for (int j=0; j< ovLength; j++)
|
|
cons[i].getParamInfo(j+1).setExpression(outerValues[j]);
|
|
cons[i].getMethodHeader().simplify();
|
|
}
|
|
}
|
|
}
|
|
|