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.
1219 lines
36 KiB
1219 lines
36 KiB
/* MethodAnalyzer 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.AssertError;
|
|
import jode.Decompiler;
|
|
import jode.GlobalOptions;
|
|
import jode.bytecode.BytecodeInfo;
|
|
import jode.bytecode.ClassInfo;
|
|
import jode.bytecode.MethodInfo;
|
|
import jode.bytecode.Handler;
|
|
import jode.bytecode.Instruction;
|
|
import jode.bytecode.LocalVariableInfo;
|
|
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.expr.LocalLoadOperator;
|
|
import jode.expr.OuterLocalOperator;
|
|
import jode.expr.InvokeOperator;
|
|
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.SimpleMap;
|
|
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.BitSet;
|
|
import java.util.Stack;
|
|
import java.util.Vector;
|
|
import java.util.Enumeration;
|
|
import java.io.DataInputStream;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
|
|
import @COLLECTIONS@.Map;
|
|
import @COLLECTIONS@.Iterator;
|
|
|
|
/**
|
|
* A method analyzer is the main class for analyzation of methods.
|
|
* There is exactly one MethodAnalyzer object for each method (even
|
|
* for abstract methods), that should be decompiled.
|
|
*
|
|
* Method analyzation is done in three passes:
|
|
* <dl>
|
|
* <dt><code>analyze()</code></dt>
|
|
* <dd>the main analyzation, decompiles the code of the method</dd>
|
|
* <dt><code>analyzeInners()</code></dt>
|
|
* <dd>This will analyze method scopes classes by calling their
|
|
* <code>analyze()</code> and <code>analyzeInners()</code>
|
|
* methods.</dd>
|
|
* <dt><code>makeDeclaration()</code></dt>
|
|
* <dd>This will determine when to declare variables. For constructors
|
|
* it will do special transformations like field initialization.</dd>
|
|
*/
|
|
public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
|
|
/**
|
|
* The import handler where we should register our types.
|
|
*/
|
|
ImportHandler imports;
|
|
/**
|
|
* The class analyzer of the class that contains this method.
|
|
*/
|
|
ClassAnalyzer classAnalyzer;
|
|
/**
|
|
* The method info structure for this method.
|
|
*/
|
|
MethodInfo minfo;
|
|
/**
|
|
* This is the bytecode info structure, or null if this method has
|
|
* no code (abstract or native).
|
|
*/
|
|
BytecodeInfo code;
|
|
|
|
/**
|
|
* The method name.
|
|
*/
|
|
String methodName;
|
|
/**
|
|
* The type of this method (parameter types + return type).
|
|
*/
|
|
MethodType methodType;
|
|
/**
|
|
* True, iff this method is a constructor, i.e. methodName == <(cl)?init>
|
|
*/
|
|
boolean isConstructor;
|
|
|
|
/**
|
|
* The exceptions this method may throw.
|
|
*/
|
|
Type[] exceptions;
|
|
|
|
/**
|
|
* If the method is synthetic (access$, class$, etc.), this is the
|
|
* synthetic analyzer describing the function of this method, otherwise
|
|
* this is null.
|
|
*/
|
|
SyntheticAnalyzer synth;
|
|
|
|
/**
|
|
* This is the first flow block of the method code. If this
|
|
* method has no code, this is null. This is initialized at the
|
|
* end of the <code>analyze()</code> phase.
|
|
*/
|
|
FlowBlock methodHeader;
|
|
/**
|
|
* A list of all locals contained in this method.
|
|
*/
|
|
Vector allLocals = new Vector();
|
|
|
|
/**
|
|
* This array contains the locals in the parameter list, including
|
|
* the implicit <i>this</i> parameter for nonstatic methods.
|
|
*/
|
|
LocalInfo[] param;
|
|
/**
|
|
* The local variable table containing info about names and types of
|
|
* locals.
|
|
*/
|
|
LocalVariableTable lvt;
|
|
|
|
/**
|
|
* True, iff this method is the special constructor, that is generated
|
|
* by jikes (constructor$xx)
|
|
*/
|
|
boolean isJikesConstructor;
|
|
/**
|
|
* 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.
|
|
*/
|
|
boolean isImplicitAnonymousConstructor;
|
|
/**
|
|
* True, if this method is the special block$ method generated by jikes
|
|
* to initialize field members.
|
|
*/
|
|
boolean isJikesBlockInitializer;
|
|
|
|
/**
|
|
* This list contains the InvokeOperator objects in the code of
|
|
* this method, that create method scoped classes. */
|
|
Vector anonConstructors = new Vector();
|
|
|
|
/**
|
|
* This list contains the class analyzers of all method scoped
|
|
* classes that should be declared in this method.
|
|
*/
|
|
Vector innerAnalyzers;
|
|
|
|
/**
|
|
* This is the default constructor.
|
|
* @param cla the ClassAnalyzer of the class that contains this method.
|
|
* @param minfo the method info structure for this method.
|
|
* @param imports the import handler that should be informed about types.
|
|
*/
|
|
public MethodAnalyzer(ClassAnalyzer cla, MethodInfo minfo,
|
|
ImportHandler imports) {
|
|
this.classAnalyzer = cla;
|
|
this.imports = imports;
|
|
this.minfo = minfo;
|
|
this.methodName = minfo.getName();
|
|
this.methodType = Type.tMethod(minfo.getType());
|
|
this.isConstructor =
|
|
methodName.equals("<init>") || methodName.equals("<clinit>");
|
|
|
|
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);
|
|
}
|
|
}
|
|
String[] excattr = minfo.getExceptions();
|
|
if (excattr == null) {
|
|
exceptions = new Type[0];
|
|
} else {
|
|
int excCount = excattr.length;
|
|
this.exceptions = new Type[excCount];
|
|
for (int i=0; i< excCount; i++)
|
|
exceptions[i] = Type.tClass(excattr[i]);
|
|
}
|
|
if (minfo.isSynthetic() || methodName.indexOf('$') != -1)
|
|
synth = new SyntheticAnalyzer(minfo, true);
|
|
}
|
|
|
|
/**
|
|
* Returns the name of this method.
|
|
*/
|
|
public String getName() {
|
|
return methodName;
|
|
}
|
|
|
|
/**
|
|
* Returns the type of this method.
|
|
* @return the type of this method.
|
|
*/
|
|
public MethodType getType() {
|
|
return methodType;
|
|
}
|
|
|
|
/**
|
|
* Returns the first flow block of the code.
|
|
* @return the first flow block of the code.
|
|
*/
|
|
public FlowBlock getMethodHeader() {
|
|
return methodHeader;
|
|
}
|
|
|
|
/**
|
|
* Returns the bytecode info for this method.
|
|
* @return the bytecode info for this method, or null if it is
|
|
* abstract or native.
|
|
*/
|
|
public final BytecodeInfo getBytecodeInfo() {
|
|
return code;
|
|
}
|
|
|
|
/**
|
|
* Returns the import handler. The import handler should be informed
|
|
* about all types we (or an expression in this method) use, so that
|
|
* the corresponding class can be imported.
|
|
* @return the import handler.
|
|
*/
|
|
public final ImportHandler getImportHandler() {
|
|
return imports;
|
|
}
|
|
|
|
/**
|
|
* Registers a type at the import handler. This should be called
|
|
* if an expression needs to print the type name to the code. The
|
|
* corresponding class will be imported in that case (if used
|
|
* often enough).
|
|
* @param type the type that should be registered.
|
|
*/
|
|
public final void useType(Type type) {
|
|
imports.useType(type);
|
|
}
|
|
|
|
/**
|
|
* Inserts a structured block to the beginning of the method.
|
|
* This is called by transform constructors, to move the super
|
|
* call from the real constructor to the constructor$xx method
|
|
* (the jikes constructor).
|
|
* @param insertBlock the structured block that should be inserted.
|
|
*/
|
|
public void insertStructuredBlock(StructuredBlock insertBlock) {
|
|
if (methodHeader != null) {
|
|
insertBlock.setJump(new Jump(FlowBlock.NEXT_BY_ADDR));
|
|
FlowBlock insertFlowBlock = new FlowBlock(this, 0);
|
|
insertFlowBlock.appendBlock(insertBlock, 0);
|
|
insertFlowBlock.setNextByAddr(methodHeader);
|
|
insertFlowBlock.doT2(methodHeader);
|
|
methodHeader = insertFlowBlock;
|
|
} else {
|
|
throw new IllegalStateException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if this method is a constructor, i.e. getName() returns
|
|
* "<init>" or "<clinit>".
|
|
* @return true, iff this method is a real constructor.
|
|
*/
|
|
public final boolean isConstructor() {
|
|
return isConstructor;
|
|
}
|
|
|
|
/**
|
|
* Checks if this method is static.
|
|
* @return true, iff this method is static.
|
|
*/
|
|
public final boolean isStatic() {
|
|
return minfo.isStatic();
|
|
}
|
|
|
|
/**
|
|
* Checks if this method is synthetic, i.e. a synthetic attribute is
|
|
* present.
|
|
* @return true, iff this method is synthetic.
|
|
*/
|
|
public final boolean isSynthetic() {
|
|
return minfo.isSynthetic();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Tells if this method is the block$xx method generated by jikes.
|
|
* @param value true, iff this method is the jikes block initializer.
|
|
*/
|
|
public final void setJikesBlockInitializer(boolean value) {
|
|
isJikesBlockInitializer = value;
|
|
}
|
|
|
|
/**
|
|
* Tells if this (constructor$xx) method has as first (implicit)
|
|
* parameter the instance of the outer class.
|
|
* @param value true, this method has the implicit parameter.
|
|
*/
|
|
public final void setHasOuterValue(boolean value) {
|
|
hasJikesOuterValue = value;
|
|
}
|
|
|
|
/**
|
|
* Tells if this constructor can be omited, since it is implicit.
|
|
* @param value true, this method is the implicit constructor.
|
|
*/
|
|
public final void setAnonymousConstructor(boolean value) {
|
|
isImplicitAnonymousConstructor = value;
|
|
}
|
|
|
|
/**
|
|
* Checks if this constructor can be omited, since it is implicit.
|
|
* @return true, this method is the implicit constructor.
|
|
*/
|
|
public final boolean isAnonymousConstructor() {
|
|
return isImplicitAnonymousConstructor;
|
|
}
|
|
|
|
/**
|
|
* Get the synthetic analyzer for this method.
|
|
* @return the synthetic analyzer, or null if this method isn't
|
|
* synthetic.
|
|
*/
|
|
public final SyntheticAnalyzer getSynthetic() {
|
|
return synth;
|
|
}
|
|
|
|
/**
|
|
* Get the return type of this method.
|
|
*/
|
|
public Type getReturnType() {
|
|
return methodType.getReturnType();
|
|
}
|
|
|
|
/**
|
|
* Get the class analyzer for the class containing this method.
|
|
*/
|
|
public ClassAnalyzer getClassAnalyzer() {
|
|
return classAnalyzer;
|
|
}
|
|
|
|
/**
|
|
* Get the class info for the class containing this method.
|
|
*/
|
|
public ClassInfo getClazz() {
|
|
return classAnalyzer.clazz;
|
|
}
|
|
|
|
/**
|
|
* Get the local info for a parameter.
|
|
* @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.
|
|
* @see #getLocalInfo
|
|
*/
|
|
public final LocalInfo getParamInfo(int nr) {
|
|
return param[nr];
|
|
}
|
|
|
|
/**
|
|
* Create a local info for a local variable located at an
|
|
* instruction with the given address.
|
|
* @param addr the address of the instruction using this local.
|
|
* the address of the next instruction for stores.
|
|
* @param slot the slot, the local variable uses.
|
|
* @return a new local info representing that local.
|
|
*/
|
|
public LocalInfo getLocalInfo(int addr, int slot) {
|
|
LocalInfo li = new LocalInfo(this, slot);
|
|
if (lvt != null) {
|
|
LocalVarEntry entry = lvt.getLocal(slot, addr);
|
|
if (entry != null)
|
|
li.addHint(entry.getName(), entry.getType());
|
|
}
|
|
allLocals.addElement(li);
|
|
return li;
|
|
}
|
|
|
|
/**
|
|
* Analyzes the code of this method. This creates the
|
|
* flow blocks (including methodHeader) and analyzes them.
|
|
*/
|
|
private 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 (Iterator i = code.getInstructions().iterator();
|
|
i.hasNext(); ) {
|
|
Instruction instr = (Instruction) i.next();
|
|
if (instr.getPrevByAddr() == null
|
|
|| instr.getPrevByAddr().doesAlwaysJump()
|
|
|| instr.getPreds() != null)
|
|
instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
|
|
}
|
|
|
|
for (int i=0; i < handlers.length; i++) {
|
|
Instruction instr = handlers[i].start;
|
|
if (instr.getTmpInfo() == null)
|
|
instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
|
|
/* end doesn't have a predecessor, but we must prevent
|
|
* it from being merged with the previous instructions.
|
|
*/
|
|
instr = handlers[i].end.getNextByAddr();
|
|
if (instr.getTmpInfo() == null)
|
|
instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
|
|
instr = handlers[i].catcher;
|
|
if (instr.getTmpInfo() == null)
|
|
instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
|
|
}
|
|
|
|
/* 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 (Iterator i = code.getInstructions().iterator();
|
|
i.hasNext(); ) {
|
|
Instruction instr = (Instruction) i.next();
|
|
|
|
jode.flow.StructuredBlock block
|
|
= Opcodes.readOpcode(instr, this);
|
|
|
|
if (GlobalOptions.verboseLevel > 0 && instr.getAddr() > mark) {
|
|
GlobalOptions.err.print('.');
|
|
mark += 1000;
|
|
}
|
|
|
|
if (lastSequential && instr.getTmpInfo() == null
|
|
/* Only merge with previous block, if this is sequential,
|
|
* too.
|
|
* Why? appendBlock does only handle sequential blocks.
|
|
*/
|
|
&& !instr.doesAlwaysJump() && instr.getSuccs() == null) {
|
|
|
|
lastBlock.appendBlock(block, instr.getLength());
|
|
} else {
|
|
|
|
if (instr.getTmpInfo() == null)
|
|
instr.setTmpInfo(new FlowBlock(this, instr.getAddr()));
|
|
FlowBlock flowBlock = (FlowBlock) instr.getTmpInfo();
|
|
flowBlock.appendBlock(block, instr.getLength());
|
|
|
|
if (lastBlock != null)
|
|
lastBlock.setNextByAddr(flowBlock);
|
|
|
|
instr.setTmpInfo(lastBlock = flowBlock);
|
|
lastSequential = !instr.doesAlwaysJump()
|
|
&& instr.getSuccs() == null;
|
|
}
|
|
}
|
|
|
|
methodHeader = (FlowBlock)
|
|
((Instruction) code.getInstructions().get(0)).getTmpInfo();
|
|
excHandlers = new TransformExceptionHandlers();
|
|
for (int i=0; i<handlers.length; i++) {
|
|
Type type = null;
|
|
FlowBlock start
|
|
= (FlowBlock) handlers[i].start.getTmpInfo();
|
|
int endAddr = handlers[i].end.getNextByAddr().getAddr();
|
|
FlowBlock handler
|
|
= (FlowBlock) handlers[i].catcher.getTmpInfo();
|
|
if (handlers[i].type != null)
|
|
type = Type.tClass(handlers[i].type);
|
|
|
|
excHandlers.addHandler(start, endAddr, handler, type);
|
|
}
|
|
}
|
|
for (Iterator i = code.getInstructions().iterator(); i.hasNext(); ) {
|
|
Instruction instr = (Instruction) i.next();
|
|
instr.setTmpInfo(null);
|
|
}
|
|
|
|
if (GlobalOptions.verboseLevel > 0)
|
|
GlobalOptions.err.print('-');
|
|
|
|
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("");
|
|
}
|
|
|
|
/**
|
|
* This is the first pass of the analyzation. It will analyze the
|
|
* code of this method, but not the method scoped classes.
|
|
*/
|
|
public void analyze()
|
|
throws ClassFormatError
|
|
{
|
|
Type[] paramTypes = getType().getParameterTypes();
|
|
int paramCount = (isStatic() ? 0 : 1) + paramTypes.length;
|
|
param = new LocalInfo[paramCount];
|
|
|
|
int offset = 0;
|
|
int slot = 0;
|
|
if (!isStatic()) {
|
|
ClassInfo classInfo = classAnalyzer.getClazz();
|
|
LocalInfo thisLocal = getLocalInfo(0, slot++);
|
|
thisLocal.setExpression(new ThisOperator(classInfo, true));
|
|
param[offset++] = thisLocal;
|
|
}
|
|
|
|
for (int i=0; i< paramTypes.length; i++) {
|
|
param[offset] = getLocalInfo(0, slot);
|
|
param[offset].setType(paramTypes[i]);
|
|
slot += paramTypes[i].stackSize();
|
|
offset++;
|
|
}
|
|
|
|
for (int i= 0; i< exceptions.length; i++)
|
|
imports.useType(exceptions[i]);
|
|
|
|
if (!isConstructor)
|
|
imports.useType(methodType.getReturnType());
|
|
|
|
if (code != null)
|
|
analyzeCode();
|
|
}
|
|
|
|
/**
|
|
* This is the second pass of the analyzation. It will analyze
|
|
* the method scoped classes.
|
|
*/
|
|
public void analyzeInnerClasses()
|
|
throws ClassFormatError
|
|
{
|
|
createAnonymousClasses();
|
|
}
|
|
|
|
/**
|
|
* This is the third and last pass of the analyzation. It will analyze
|
|
* the types and names of the local variables and where to declare them.
|
|
* 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();
|
|
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 */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (code != null) {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tells if this method is synthetic or implicit or something else, so
|
|
* that it doesn't have to be written to the source code.
|
|
* @return true, iff it shouldn't be written to the source code.
|
|
*/
|
|
public boolean skipWriting() {
|
|
if (synth != null) {
|
|
// We don't need this class anymore (hopefully?)
|
|
if (synth.getKind() == synth.GETCLASS)
|
|
return true;
|
|
if (synth.getKind() >= synth.ACCESSGETFIELD
|
|
&& synth.getKind() <= synth.ACCESSSTATICMETHOD
|
|
&& (Decompiler.options & Decompiler.OPTION_INNER) != 0
|
|
&& (Decompiler.options & Decompiler.OPTION_ANON) != 0)
|
|
return true;
|
|
}
|
|
|
|
if (isConstructor && isJikesConstructor) {
|
|
// This is the first empty part of a jikes constructor
|
|
return true;
|
|
}
|
|
|
|
boolean declareAsConstructor = isConstructor;
|
|
int skipParams = 0;
|
|
if (isConstructor() && !isStatic()
|
|
&& classAnalyzer.outerValues != null)
|
|
skipParams = classAnalyzer.outerValues.length;
|
|
|
|
if (isJikesConstructor) {
|
|
// This is the real part of a jikes constructor
|
|
declareAsConstructor = true;
|
|
skipParams = hasJikesOuterValue
|
|
&& classAnalyzer.outerValues.length > 0 ? 1 : 0;
|
|
}
|
|
|
|
if (isJikesBlockInitializer)
|
|
return true;
|
|
|
|
if (declareAsConstructor
|
|
/* The access rights of default constructor should
|
|
* be public, iff the class is public, otherwise package.
|
|
* But this rule doesn't necessarily apply for anonymous
|
|
* classes...
|
|
*/
|
|
&& ((minfo.getModifiers()
|
|
& (Modifier.PROTECTED | Modifier.PUBLIC))
|
|
== (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 (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)
|
|
return true;
|
|
}
|
|
|
|
if (isConstructor() && isStatic()
|
|
&& getMethodHeader().getBlock() instanceof jode.flow.EmptyBlock)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Dumps the source code for this method to the specified writer.
|
|
* @param writer the tabbed print writer the code should be written to.
|
|
* @exception IOException, if writer throws an exception.
|
|
*/
|
|
public void dumpSource(TabbedPrintWriter writer)
|
|
throws IOException
|
|
{
|
|
boolean declareAsConstructor = isConstructor;
|
|
int skipParams = 0;
|
|
if (isConstructor() && !isStatic()
|
|
&& (Decompiler.options & Decompiler.OPTION_CONTRAFO) != 0
|
|
&& classAnalyzer.outerValues != null)
|
|
skipParams = classAnalyzer.outerValues.length;
|
|
|
|
if (isJikesConstructor) {
|
|
// This is the real part of a jikes constructor
|
|
declareAsConstructor = true;
|
|
skipParams = hasJikesOuterValue
|
|
&& classAnalyzer.outerValues.length > 0 ? 1 : 0;
|
|
}
|
|
|
|
if (minfo.isDeprecated()) {
|
|
writer.println("/**");
|
|
writer.println(" * @deprecated");
|
|
writer.println(" */");
|
|
}
|
|
|
|
writer.pushScope(this);
|
|
|
|
if (minfo.isSynthetic()
|
|
&& (classAnalyzer.getName() != null
|
|
|| !isConstructor()))
|
|
writer.print("/*synthetic*/ ");
|
|
int modifiedModifiers = minfo.getModifiers();
|
|
/*
|
|
* JLS-1.0, section 9.4:
|
|
*
|
|
* For compatibility with older versions of Java, it is
|
|
* permitted but discouraged, as a matter of style, to
|
|
* redundantly specify the abstract modifier for methods
|
|
* declared in interfaces.
|
|
*
|
|
* Every method declaration in the body of an interface is
|
|
* implicitly public. It is permitted, but strongly
|
|
* discouraged as a matter of style, to redundantly specify
|
|
* the public modifier for interface methods. We don't
|
|
* follow this second rule and mark this method explicitly
|
|
* as public.
|
|
*/
|
|
if (classAnalyzer.getClazz().isInterface())
|
|
modifiedModifiers &= ~Modifier.ABSTRACT;
|
|
String modif = Modifier.toString(modifiedModifiers);
|
|
if (modif.length() > 0)
|
|
writer.print(modif+" ");
|
|
if (isConstructor
|
|
&& (isStatic()
|
|
|| (classAnalyzer.getName() == null
|
|
&& skipParams == methodType.getParameterTypes().length))) {
|
|
/* static block or unnamed constructor */
|
|
} else {
|
|
if (declareAsConstructor)
|
|
writer.print(classAnalyzer.getName());
|
|
else {
|
|
writer.printType(getReturnType());
|
|
writer.print(" " + methodName);
|
|
}
|
|
writer.print("(");
|
|
int offset = skipParams + (isStatic() ? 0 : 1);
|
|
for (int i = offset; i < param.length; i++) {
|
|
if (i > offset)
|
|
writer.print(", ");
|
|
param[i].dumpDeclaration(writer);
|
|
}
|
|
writer.print(")");
|
|
}
|
|
if (exceptions.length > 0) {
|
|
writer.println("");
|
|
writer.print("throws ");
|
|
for (int i= 0; i< exceptions.length; i++) {
|
|
if (i > 0)
|
|
writer.print(", ");
|
|
writer.printType(exceptions[i]);
|
|
}
|
|
}
|
|
if (code != null) {
|
|
writer.openBrace();
|
|
writer.tab();
|
|
methodHeader.dumpSource(writer);
|
|
writer.untab();
|
|
writer.closeBrace();
|
|
} else
|
|
writer.println(";");
|
|
writer.popScope();
|
|
}
|
|
|
|
/**
|
|
* Checks if the variable set contains a local with the given name.
|
|
* @return the local info the has the given name, or null if it doesn't
|
|
* exists.
|
|
*/
|
|
public LocalInfo findLocal(String name) {
|
|
Enumeration enum = allLocals.elements();
|
|
while (enum.hasMoreElements()) {
|
|
LocalInfo li = (LocalInfo) enum.nextElement();
|
|
if (li.getName().equals(name))
|
|
return li;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if a method scoped class with the given name exists in this
|
|
* method (not in a parent method).
|
|
* @return the class analyzer with the given name, or null if it
|
|
* doesn' exists.
|
|
*/
|
|
public ClassAnalyzer findAnonClass(String name) {
|
|
if (innerAnalyzers != null) {
|
|
Enumeration enum = innerAnalyzers.elements();
|
|
while (enum.hasMoreElements()) {
|
|
ClassAnalyzer classAna = (ClassAnalyzer) enum.nextElement();
|
|
if (classAna.getParent() == this
|
|
&& classAna.getName() != null
|
|
&& classAna.getName().equals(name)) {
|
|
return classAna;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified object lies in this scope.
|
|
* @param obj the object.
|
|
* @param scopeType the type of this object.
|
|
*/
|
|
public boolean isScopeOf(Object obj, int scopeType) {
|
|
if (scopeType == METHODSCOPE
|
|
&& obj instanceof ClassInfo) {
|
|
ClassAnalyzer ana = getClassAnalyzer((ClassInfo)obj);
|
|
if (ana != null)
|
|
return ana.getParent() == this;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the specified name conflicts with an object in this scope.
|
|
* @param name the name to check.
|
|
* @param scopeType the usage type of this name, AMBIGUOUSNAME if it is
|
|
* ambiguous.
|
|
*/
|
|
public boolean conflicts(String name, int usageType) {
|
|
if (usageType == AMBIGUOUSNAME || usageType == LOCALNAME)
|
|
return findLocal(name) != null;
|
|
if (usageType == AMBIGUOUSNAME || usageType == CLASSNAME)
|
|
return findAnonClass(name) != null;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the parent scope, i.e. the class analyzer for the class containing
|
|
* this method.
|
|
* @XXX needed?
|
|
*/
|
|
public ClassDeclarer getParent() {
|
|
return getClassAnalyzer();
|
|
}
|
|
|
|
/**
|
|
* Registers an anonymous constructor invokation. This should be called
|
|
* in the analyze or analyzeInner pass by invoke subexpressions.
|
|
* @param cop the constructor invokation, that creates the method scoped
|
|
* class.
|
|
*/
|
|
public void addAnonymousConstructor(InvokeOperator cop) {
|
|
anonConstructors.addElement(cop);
|
|
}
|
|
|
|
private boolean unifyOuterValues(Expression ov1, Expression ov2,
|
|
final ClassAnalyzer clazzAna,
|
|
final int shrinkTo) {
|
|
|
|
|
|
/* Wow, unifying outer values of different constructors in
|
|
* different methods of different classes can get complicated.
|
|
* We have not committed the number of OuterValues. So we
|
|
* can't say for sure, if the local load matches an outer
|
|
* local if this is a constructor. Even worse: The previous
|
|
* outerValues may be a load of a constructor local, that
|
|
* should be used as outer value...
|
|
*
|
|
* We look if there is a way to merge them and register an
|
|
* outer value listener to lots of classes.
|
|
*/
|
|
|
|
LocalInfo li1 = null;
|
|
MethodAnalyzer method1 = null;
|
|
if (ov2 instanceof ThisOperator) {
|
|
if (ov1 instanceof ThisOperator)
|
|
return ov1.equals(ov2);
|
|
Expression temp = ov2;
|
|
ov2 = ov1;
|
|
ov1 = temp;
|
|
|
|
} else {
|
|
|
|
if (ov1 instanceof LocalLoadOperator)
|
|
li1 = ((LocalLoadOperator) ov1).getLocalInfo();
|
|
else if (ov1 instanceof OuterLocalOperator)
|
|
li1 = ((OuterLocalOperator) ov1).getLocalInfo();
|
|
else if (!(ov1 instanceof ThisOperator))
|
|
return false;
|
|
}
|
|
|
|
LocalInfo li2;
|
|
if (ov2 instanceof LocalLoadOperator)
|
|
li2 = ((LocalLoadOperator) ov2).getLocalInfo();
|
|
else if (ov2 instanceof OuterLocalOperator)
|
|
li2 = ((OuterLocalOperator) ov2).getLocalInfo();
|
|
else
|
|
return false;
|
|
MethodAnalyzer method2 = li2.getMethodAnalyzer();
|
|
|
|
|
|
/* Now: li2 != null, method2 != null
|
|
* (li1 == null and method1 == null) iff ov1 is ThisOperator
|
|
*/
|
|
|
|
class ShrinkOnShrink implements OuterValueListener {
|
|
Map limits = new SimpleMap();
|
|
|
|
public void setLimit(ClassAnalyzer other,
|
|
int newLimit) {
|
|
limits.put(other, new Integer(newLimit));
|
|
other.addOuterValueListener(this);
|
|
}
|
|
|
|
public void done() {
|
|
limits = null;
|
|
}
|
|
|
|
public void shrinkingOuterValues
|
|
(ClassAnalyzer other, int newCount) {
|
|
if (limits != null) {
|
|
int limit = ((Integer) limits.get(other)
|
|
).intValue();
|
|
if (newCount <= limit) {
|
|
clazzAna.shrinkOuterValues(shrinkTo);
|
|
done();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ShrinkOnShrink sos = new ShrinkOnShrink();
|
|
|
|
if (li1 != null) {
|
|
method1 = li1.getMethodAnalyzer();
|
|
|
|
while (!method2.isParent(method1)) {
|
|
if (!method1.isConstructor() || method1.isStatic()) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
|
|
ClassAnalyzer ca1 = method1.classAnalyzer;
|
|
int slot = li1.getSlot();
|
|
Expression[] ov = ca1.getOuterValues();
|
|
if (ov == null) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
|
|
int param = 0;
|
|
while (param < ov.length && slot > 0)
|
|
slot -= ov[param++].getType().stackSize();
|
|
|
|
if (slot != 0) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
ov1 = ov[param];
|
|
sos.setLimit(ca1, param);
|
|
|
|
if (ov1 instanceof ThisOperator) {
|
|
li1 = null;
|
|
method1 = null;
|
|
break;
|
|
}
|
|
li1 = ((OuterLocalOperator) ov1).getLocalInfo();
|
|
method1 = li1.getMethodAnalyzer();
|
|
}
|
|
}
|
|
|
|
/* Now: ov1 is ThisOperator and method1 == null
|
|
* or (ov1 is LocalExpression, li1 is LocalInfo,
|
|
* method1 is parent of method2).
|
|
*/
|
|
while (method1 != method2) {
|
|
if (!method2.isConstructor() || method2.isStatic()) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
|
|
ClassAnalyzer ca2 = method2.classAnalyzer;
|
|
int slot = li2.getSlot();
|
|
Expression[] ov = ca2.getOuterValues();
|
|
if (ov == null) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
|
|
slot--;
|
|
int param = 0;
|
|
while (param < ov.length && slot > 0)
|
|
slot -= ov[param++].getType().stackSize();
|
|
|
|
if (slot != 0) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
|
|
ov2 = ov[param];
|
|
sos.setLimit(ca2, param);
|
|
if (ov2 instanceof ThisOperator) {
|
|
if (ov1.equals(ov2))
|
|
return true;
|
|
else {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
li2 = ((OuterLocalOperator) ov2).getLocalInfo();
|
|
method2 = li2.getMethodAnalyzer();
|
|
}
|
|
if (!li1.equals(li2)) {
|
|
sos.done();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void analyzeInvokeOperator(InvokeOperator cop) {
|
|
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[] subExprs = cop.getSubExpressions();
|
|
outerValues = new Expression[subExprs.length-1];
|
|
|
|
for (int j=0; j < outerValues.length; j++) {
|
|
Expression expr = subExprs[j+1].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+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))
|
|
continue;
|
|
|
|
}
|
|
anonAnalyzer.shrinkOuterValues(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
* 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 getClass().getName()+"["+getClazz()+"."+getName()+"]";
|
|
}
|
|
}
|
|
|