diff --git a/jode/jode/decompiler/MethodAnalyzer.java b/jode/jode/decompiler/MethodAnalyzer.java index 72eec2d..f678648 100644 --- a/jode/jode/decompiler/MethodAnalyzer.java +++ b/jode/jode/decompiler/MethodAnalyzer.java @@ -18,18 +18,36 @@ */ package jode.decompiler; -import jode.bytecode.ClassInfo; -import jode.bytecode.MethodInfo; +import jode.AssertError; +import jode.Decompiler; +import jode.GlobalOptions; +import jode.bytecode.*; import jode.jvm.SyntheticAnalyzer; import jode.type.*; import jode.expr.Expression; +import jode.expr.ConstOperator; +import jode.expr.CheckNullOperator; import jode.expr.ThisOperator; -import jode.AssertError; -import jode.Decompiler; -import jode.GlobalOptions; +import jode.expr.LocalLoadOperator; +import jode.expr.OuterLocalOperator; +import jode.expr.ConstructorOperator; +import jode.flow.StructuredBlock; +import jode.flow.FlowBlock; +import jode.flow.TransformExceptionHandlers; +import jode.flow.Jump; +import jode.jvm.CodeVerifier; +import jode.jvm.VerifyException; +import jode.util.SimpleDictionary; import java.lang.reflect.Modifier; -import java.io.*; +import java.util.BitSet; +import java.util.Stack; +import java.util.Vector; +import java.util.Dictionary; +import java.util.Enumeration; +import java.io.DataInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { ImportHandler imports; @@ -40,14 +58,34 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { MethodType methodType; boolean isConstructor; - CodeAnalyzer code = null; Type[] exceptions; SyntheticAnalyzer synth; + FlowBlock methodHeader; + BytecodeInfo code; + + Vector allLocals = new Vector(); + LocalInfo[] param; + LocalVariableTable lvt; + + /** + * This is a block that will be inserted at the beginning of the + * method, when the code is analyzed. + */ + StructuredBlock insertBlock = null; + boolean isJikesConstructor; boolean isImplicitAnonymousConstructor; + /** + * This dictionary maps an anonymous ClassInfo to the + * ConstructorOperator that creates this class. + */ + Vector anonConstructors = new Vector(); + Vector innerAnalyzers; + + public MethodAnalyzer(ClassAnalyzer cla, MethodInfo minfo, ImportHandler imports) { this.classAnalyzer = cla; @@ -58,8 +96,28 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { this.isConstructor = methodName.equals("") || methodName.equals(""); - if (minfo.getBytecode() != null) - code = new CodeAnalyzer(this, minfo, imports); + if (minfo.getBytecode() != null) { + code = minfo.getBytecode(); + + if ((Decompiler.options & Decompiler.OPTION_VERIFY) != 0) { + CodeVerifier verifier + = new CodeVerifier(getClazz(), minfo, code); + try { + verifier.verify(); + } catch (VerifyException ex) { + ex.printStackTrace(GlobalOptions.err); + throw new jode.AssertError("Verification error"); + } + } + + if ((Decompiler.options & Decompiler.OPTION_LVT) != 0) { + LocalVariableInfo[] localvars = code.getLocalVariableTable(); + if (localvars != null) + lvt = new LocalVariableTable(code.getMaxLocals(), + localvars); + } + initParams(); + } String[] excattr = minfo.getExceptions(); if (excattr == null) { exceptions = new Type[0]; @@ -73,6 +131,22 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { synth = new SyntheticAnalyzer(minfo, true); } + + + public void initParams() { + Type[] paramTypes = getType().getParameterTypes(); + int paramCount = (isStatic() ? 0 : 1) + paramTypes.length; + param = new LocalInfo[paramCount]; + int offset = 0; + int slot = 0; + if (!isStatic()) + param[offset++] = getLocalInfo(0, slot++); + for (int i=0; i < paramTypes.length; i++) { + param[offset++] = getLocalInfo(0, slot); + slot += paramTypes[i].stackSize(); + } + } + public String getName() { return methodName; } @@ -81,14 +155,28 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { return methodType; } - public jode.flow.FlowBlock getMethodHeader() { - return code != null ? code.getMethodHeader() : null; + public FlowBlock getMethodHeader() { + return methodHeader; } - public CodeAnalyzer getCode() { + public final BytecodeInfo getBytecodeInfo() { return code; } + public final ImportHandler getImportHandler() { + return imports; + } + + public final void useType(Type type) { + imports.useType(type); + } + + public void insertStructuredBlock(StructuredBlock superBlock) { + if (insertBlock != null) + throw new jode.AssertError(); + insertBlock = superBlock; + } + public final boolean isConstructor() { return isConstructor; } @@ -121,6 +209,134 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { return methodType.getReturnType(); } + + public void analyzeCode() + { + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.print(methodName+": "); + /* The adjacent analyzation relies on this */ + DeadCodeAnalysis.removeDeadCode(code); + Handler[] handlers = code.getExceptionHandlers(); + int returnCount; + TransformExceptionHandlers excHandlers; + { + /* First create a FlowBlock for every block that has a + * predecessor other than the previous instruction. + */ + for (Instruction instr = code.getFirstInstr(); + instr != null; instr = instr.nextByAddr) { + if (instr.prevByAddr == null + || instr.prevByAddr.alwaysJumps + || instr.preds != null) + instr.tmpInfo = new FlowBlock + (this, instr.addr, instr.length); + } + + for (int i=0; i < handlers.length; i++) { + Instruction instr = handlers[i].start; + if (instr.tmpInfo == null) + instr.tmpInfo + = new FlowBlock(this, instr.addr, instr.length); + instr = handlers[i].catcher; + if (instr.tmpInfo == null) + instr.tmpInfo + = new FlowBlock(this, instr.addr, instr.length); + } + + /* While we read the opcodes into FlowBlocks + * we try to combine sequential blocks, as soon as we + * find two sequential instructions in a row, where the + * second has no predecessors. + */ + int mark = 1000; + FlowBlock lastBlock = null; + boolean lastSequential = false; + for (Instruction instr = code.getFirstInstr(); + instr != null; instr = instr.nextByAddr) { + + jode.flow.StructuredBlock block + = Opcodes.readOpcode(instr, this); + + if (GlobalOptions.verboseLevel > 0 && instr.addr > mark) { + GlobalOptions.err.print('.'); + mark += 1000; + } + + if (lastSequential && instr.tmpInfo == null + /* Only merge with previous block, if this is sequential, + * too. + * Why? doSequentialT2 does only handle sequential blocks. + */ + && !instr.alwaysJumps && instr.succs == null) { + + lastBlock.doSequentialT2(block, instr.length); + + } else { + + if (instr.tmpInfo == null) + instr.tmpInfo = new FlowBlock + (this, instr.addr, instr.length); + FlowBlock flowBlock = (FlowBlock) instr.tmpInfo; + flowBlock.setBlock(block); + + if (lastBlock != null) + lastBlock.setNextByAddr(flowBlock); + + instr.tmpInfo = lastBlock = flowBlock; + lastSequential = !instr.alwaysJumps && instr.succs == null; + } + } + + methodHeader = (FlowBlock) code.getFirstInstr().tmpInfo; + if (insertBlock != null) { + insertBlock.setJump(new Jump(methodHeader)); + FlowBlock insertFlowBlock = new FlowBlock(this, 0, 0); + insertFlowBlock.setBlock(insertBlock); + insertFlowBlock.setNextByAddr(methodHeader); + methodHeader = insertFlowBlock; + } + + excHandlers = new TransformExceptionHandlers(); + for (int i=0; i 0) + GlobalOptions.err.print('-'); + +// try { +// TabbedPrintWriter writer = new TabbedPrintWriter(System.err); +// methodHeader.dumpSource(writer); +// } catch (java.io.IOException ex) { +// } + excHandlers.analyze(); + methodHeader.analyze(); + + if ((Decompiler.options & Decompiler.OPTION_PUSH) == 0 + && methodHeader.mapStackToLocal()) + methodHeader.removePush(); + if ((Decompiler.options & Decompiler.OPTION_ONETIME) != 0) + methodHeader.removeOnetimeLocals(); + + methodHeader.mergeParams(param); + + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println(""); + } + public void analyze() throws ClassFormatError { @@ -130,14 +346,14 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { int offset = 0; if (!isStatic()) { ClassInfo classInfo = classAnalyzer.getClazz(); - LocalInfo thisLocal = code.getParamInfo(0); + LocalInfo thisLocal = getParamInfo(0); thisLocal.setExpression(new ThisOperator(classInfo, true)); offset++; } Type[] paramTypes = methodType.getParameterTypes(); for (int i=0; i< paramTypes.length; i++) { - code.getParamInfo(offset).setType(paramTypes[i]); + getParamInfo(offset).setType(paramTypes[i]); offset++; } @@ -147,28 +363,18 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { if (!isConstructor) imports.useType(methodType.getReturnType()); - if ((Decompiler.options & Decompiler.OPTION_IMMEDIATE) == 0) { - if (GlobalOptions.verboseLevel > 0) - GlobalOptions.err.print(methodName+": "); - code.analyze(); - if (GlobalOptions.verboseLevel > 0) - GlobalOptions.err.println(""); - } + if ((Decompiler.options & Decompiler.OPTION_IMMEDIATE) == 0) + analyzeCode(); } - public LocalInfo getParamInfo(int i) { - if (code == null) - return null; - return code.getParamInfo(i); + public final LocalInfo getParamInfo(int nr) { + return param[nr]; } - public void analyzeAnonymousClasses() + public void analyzeInnerClasses() throws ClassFormatError { - if (code == null) - return; - - code.analyzeAnonymousClasses(); + createAnonymousClasses(); } public void makeDeclaration() { @@ -176,13 +382,48 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { && classAnalyzer.outerValues != null) { Expression[] outerValues = classAnalyzer.outerValues; for (int i=0; i< outerValues.length; i++) { - LocalInfo local = code.getParamInfo(1+i); + LocalInfo local = getParamInfo(1+i); local.setExpression(outerValues[i]); } } - if (code != null) - code.makeDeclaration(); + for (Enumeration enum = allLocals.elements(); + enum.hasMoreElements(); ) { + LocalInfo li = (LocalInfo)enum.nextElement(); + if (!li.isShadow()) + imports.useType(li.getType()); + } + for (int i=0; i < param.length; i++) { + param[i].guessName(); + for (int j=0; j < i; j++) { + if (param[j].getName().equals(param[i].getName())) { + /* A name conflict happened. */ + param[i].makeNameUnique(); + break; /* j */ + } + } + } + + methodHeader.makeDeclaration(param); + methodHeader.simplify(); + if (innerAnalyzers != null) { + for (Enumeration enum = innerAnalyzers.elements(); + 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) { + LocalInfo li = ((OuterLocalOperator) + outerValues[i]).getLocalInfo(); + if (li.getMethodAnalyzer() == this) + li.markFinal(); + } + } + classAna.makeDeclaration(); + } + } + } } public boolean skipWriting() { @@ -255,11 +496,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { // We do the code.analyze() here, to get // immediate output. - if (GlobalOptions.verboseLevel > 0) - GlobalOptions.err.print(methodName+": "); - code.analyze(); - if (GlobalOptions.verboseLevel > 0) - GlobalOptions.err.println(""); + analyzeCode(); } if (isConstructor() && isStatic() @@ -271,7 +508,12 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { writer.println(" * @deprecated"); writer.println(" */"); } - if (minfo.isSynthetic()) + + writer.pushScope(this); + + if (minfo.isSynthetic() + && (classAnalyzer.getName() != null + || !isConstructor())) writer.print("/*synthetic*/ "); int modifiedModifiers = minfo.getModifiers(); /* @@ -295,9 +537,9 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { if (isConstructor && (isStatic() || (classAnalyzer.getName() == null - && skipParams == methodType.getParameterTypes().length))) - writer.print(""); /* static block or unnamed constructor */ - else { + && skipParams == methodType.getParameterTypes().length))) { + /* static block or unnamed constructor */ + } else { if (declareAsConstructor) writer.print(classAnalyzer.getName()); else { @@ -312,7 +554,7 @@ public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer { LocalInfo[] param = new LocalInfo[paramTypes.length]; for (int i=start; i 0) + slot -= ov[param++].getType().stackSize(); + + if (slot != 0) { + sos.done(); + return false; + } + ov1 = ov[param]; + + if (ov1 instanceof ThisOperator) { + li1 = null; + method1 = null; + break; + } + + sos.setLimit(ca1, param); + li1 = ((OuterLocalOperator) ov1).getLocalInfo(); + method1 = li1.getMethodAnalyzer(); + System.err.println("unifyLocalInfos: "+method1+"."+li1 + +" and "+method2+"."+li2); + } + } + + /* Now: ov1 is ThisOperator and method1 == null + * or (ov1 is LocalExpression, li1 is LocalInfo, + * method1 is parent of method2). + */ + + System.err.println(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) { + System.err.println("slot: "+slot+"; param: "+param+"; "+ov[param]); + sos.done(); + return false; + } + + ov2 = ov[param]; + if (ov2 instanceof ThisOperator) { + if (ov1.equals(ov2)) + return true; + else { + sos.done(); + return false; + } + } + + sos.setLimit(ca2, param); + li2 = ((OuterLocalOperator) ov2).getLocalInfo(); + method2 = li2.getMethodAnalyzer(); + System.err.println("unifyLocalInfos: "+method1+"."+li1 + +" and "+method2+"."+li2); + } + if (!li1.equals(li2)) { + sos.done(); + return false; + } + return true; + } + + public void analyzeConstructorOperator(ConstructorOperator 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. + */ + if (GlobalOptions.verboseLevel > 0) + GlobalOptions.err.println("Analyzing method scope class: " + +clazz); + Expression[] subExprs = cop.getSubExpressions(); + outerValues = new Expression[subExprs.length]; + + for (int j=0; j < outerValues.length; j++) { + Expression expr = subExprs[j].simplify(); + if (expr instanceof CheckNullOperator) + expr = ((CheckNullOperator) + expr).getSubExpressions()[0]; + if (expr instanceof ThisOperator) { + outerValues[j] = + new ThisOperator(((ThisOperator) + expr).getClassInfo()); + continue; + } + LocalInfo li = null; + if (expr instanceof LocalLoadOperator) { + li = ((LocalLoadOperator) expr).getLocalInfo(); + if (!li.isConstant()) + li = null; + } + if (expr instanceof OuterLocalOperator) + li = ((OuterLocalOperator) expr).getLocalInfo(); + + if (li != null) { + outerValues[j] = new OuterLocalOperator(li); + continue; + } + + Expression[] newOuter = new Expression[j]; + System.arraycopy(outerValues, 0, newOuter, 0, j); + outerValues = newOuter; + break; + } + anonAnalyzer = new ClassAnalyzer(this, clazz, imports, + outerValues); + addClassAnalyzer(anonAnalyzer); + anonAnalyzer.analyze(); + anonAnalyzer.analyzeInnerClasses(); + } else { + + /* + * Get the previously created outerValues array and + * its length. + */ + 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++) { + if (j < subExprs.length) { + Expression expr = subExprs[j].simplify(); + if (expr instanceof CheckNullOperator) + expr = ((CheckNullOperator) expr) + .getSubExpressions()[0]; + + if (unifyOuterValues(outerValues[j], expr, + anonAnalyzer, j)) + continue; + + System.err.println("shrinkOuterValues: " + +outerValues[j]+" vs. "+expr); + } + anonAnalyzer.shrinkOuterValues(j); + break; + } + } + } + + public void createAnonymousClasses() { + int serialnr = 0; + Enumeration elts = anonConstructors.elements(); + while (elts.hasMoreElements()) { + ConstructorOperator cop = (ConstructorOperator) elts.nextElement(); + analyzeConstructorOperator(cop); + } + } + + /** + * Get the class analyzer for the given class info. This searches + * the method scoped/anonymous classes in this method and all + * outer methods and the outer classes for the class analyzer. + * @param cinfo the classinfo for which the analyzer is searched. + * @return the class analyzer, or null if there is not an outer + * class that equals cinfo, and not a method scope/inner class in + * an outer method. + */ + public ClassAnalyzer getClassAnalyzer(ClassInfo cinfo) { + if (innerAnalyzers != null) { + Enumeration enum = innerAnalyzers.elements(); + 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); + } + } + classAna.setParent(this); + } + return classAna; + } + } + } + return getParent().getClassAnalyzer(cinfo); + } + + public void addClassAnalyzer(ClassAnalyzer clazzAna) { + if (innerAnalyzers == null) + innerAnalyzers = new Vector(); + innerAnalyzers.addElement(clazzAna); + getParent().addClassAnalyzer(clazzAna); + } + + public boolean isParent(ClassDeclarer declarer) { + ClassDeclarer ancestor = this; + while (ancestor != null) { + if (ancestor == declarer) + return true; + ancestor = ancestor.getParent(); + } + return false; + } + + public String toString() { + return "MethodAnalyzer["+getClazz()+"."+getName()+"]"; + } }