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.
1017 lines
29 KiB
1017 lines
29 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;
|
|
|
|
public class MethodAnalyzer implements Analyzer, Scope, ClassDeclarer {
|
|
ImportHandler imports;
|
|
ClassAnalyzer classAnalyzer;
|
|
MethodInfo minfo;
|
|
|
|
String methodName;
|
|
MethodType methodType;
|
|
boolean isConstructor;
|
|
|
|
Type[] exceptions;
|
|
|
|
SyntheticAnalyzer synth;
|
|
|
|
FlowBlock methodHeader;
|
|
BytecodeInfo code;
|
|
|
|
Vector allLocals = new Vector();
|
|
LocalInfo[] param;
|
|
LocalVariableTable lvt;
|
|
|
|
boolean isJikesConstructor;
|
|
boolean hasJikesOuterValue;
|
|
boolean isImplicitAnonymousConstructor;
|
|
boolean isJikesBlockInitializer;
|
|
|
|
/**
|
|
* This dictionary maps an anonymous ClassInfo to the
|
|
* InvokeOperator that creates this class.
|
|
*/
|
|
Vector anonConstructors = new Vector();
|
|
Vector innerAnalyzers;
|
|
|
|
|
|
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);
|
|
}
|
|
initParams();
|
|
}
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
public MethodType getType() {
|
|
return methodType;
|
|
}
|
|
|
|
public FlowBlock getMethodHeader() {
|
|
return methodHeader;
|
|
}
|
|
|
|
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 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();
|
|
}
|
|
}
|
|
|
|
public final boolean isConstructor() {
|
|
return isConstructor;
|
|
}
|
|
|
|
public final boolean isStatic() {
|
|
return minfo.isStatic();
|
|
}
|
|
|
|
public final boolean isSynthetic() {
|
|
return minfo.isSynthetic();
|
|
}
|
|
|
|
public final void setJikesConstructor(boolean value) {
|
|
isJikesConstructor = value;
|
|
}
|
|
|
|
public final void setJikesBlockInitializer(boolean value) {
|
|
isJikesBlockInitializer = value;
|
|
}
|
|
|
|
public final void setHasOuterValue(boolean value) {
|
|
hasJikesOuterValue = value;
|
|
}
|
|
|
|
public final void setAnonymousConstructor(boolean value) {
|
|
isImplicitAnonymousConstructor = value;
|
|
}
|
|
|
|
public final boolean isAnonymousConstructor() {
|
|
return isImplicitAnonymousConstructor;
|
|
}
|
|
|
|
public final SyntheticAnalyzer getSynthetic() {
|
|
return synth;
|
|
}
|
|
|
|
public Type getReturnType() {
|
|
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 (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("");
|
|
}
|
|
|
|
public void analyze()
|
|
throws ClassFormatError
|
|
{
|
|
if (code == null)
|
|
return;
|
|
|
|
int offset = 0;
|
|
if (!isStatic()) {
|
|
ClassInfo classInfo = classAnalyzer.getClazz();
|
|
LocalInfo thisLocal = getParamInfo(0);
|
|
thisLocal.setExpression(new ThisOperator(classInfo, true));
|
|
offset++;
|
|
}
|
|
|
|
Type[] paramTypes = methodType.getParameterTypes();
|
|
for (int i=0; i< paramTypes.length; i++) {
|
|
getParamInfo(offset).setType(paramTypes[i]);
|
|
offset++;
|
|
}
|
|
|
|
for (int i= 0; i< exceptions.length; i++)
|
|
imports.useType(exceptions[i]);
|
|
|
|
if (!isConstructor)
|
|
imports.useType(methodType.getReturnType());
|
|
|
|
analyzeCode();
|
|
}
|
|
|
|
public final LocalInfo getParamInfo(int nr) {
|
|
return param[nr];
|
|
}
|
|
|
|
public void analyzeInnerClasses()
|
|
throws ClassFormatError
|
|
{
|
|
createAnonymousClasses();
|
|
}
|
|
|
|
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 = getParamInfo(1+i);
|
|
local.setExpression(outerValues[i]);
|
|
}
|
|
}
|
|
if (isJikesConstructor && hasJikesOuterValue
|
|
&& classAnalyzer.outerValues != null
|
|
&& classAnalyzer.outerValues.length > 0)
|
|
getParamInfo(1).setExpression(classAnalyzer.outerValues[0]);
|
|
|
|
for (Enumeration enum = allLocals.elements();
|
|
enum.hasMoreElements(); ) {
|
|
LocalInfo li = (LocalInfo)enum.nextElement();
|
|
if (!li.isShadow())
|
|
imports.useType(li.getType());
|
|
}
|
|
if (code != null) {
|
|
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() {
|
|
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;
|
|
}
|
|
|
|
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("(");
|
|
Type[] paramTypes = methodType.getParameterTypes();
|
|
int offset = skipParams + (isStatic() ? 0 : 1);
|
|
int start = skipParams;
|
|
|
|
LocalInfo[] param = new LocalInfo[paramTypes.length];
|
|
for (int i=start; i<paramTypes.length; i++) {
|
|
if (code == null) {
|
|
param[i] = new LocalInfo(this, offset);
|
|
param[i].setType(paramTypes[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 */
|
|
}
|
|
}
|
|
} else {
|
|
param[i] = getParamInfo(offset);
|
|
offset++;
|
|
}
|
|
}
|
|
|
|
for (int i=start; i<paramTypes.length; i++) {
|
|
if (i>start)
|
|
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();
|
|
if (methodHeader != null)
|
|
methodHeader.dumpSource(writer);
|
|
else
|
|
writer.println("COULDN'T DECOMPILE METHOD!");
|
|
writer.untab();
|
|
writer.closeBrace();
|
|
} else
|
|
writer.println(";");
|
|
writer.popScope();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public ClassAnalyzer getClassAnalyzer() {
|
|
return classAnalyzer;
|
|
}
|
|
|
|
public ClassInfo getClazz() {
|
|
return classAnalyzer.clazz;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks if the variable set contains a local with the given name.
|
|
*/
|
|
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 an anonymous class with the given name 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public ClassDeclarer getParent() {
|
|
return getClassAnalyzer();
|
|
}
|
|
|
|
|
|
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()+"]";
|
|
}
|
|
}
|
|
|