Mirror of the JODE repository
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.
 
 
 
 
 
 
jode/jode/jode/expr/InvokeOperator.java.in

1138 lines
34 KiB

/* InvokeOperator 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.expr;
import java.lang.reflect.Modifier;
import jode.decompiler.MethodAnalyzer;
import jode.decompiler.MethodAnalyzer;
import jode.decompiler.ClassAnalyzer;
import jode.decompiler.TabbedPrintWriter;
import jode.decompiler.Options;
import jode.decompiler.OuterValues;
import jode.decompiler.Scope;
import jode.GlobalOptions;
import jode.bytecode.*;
import jode.jvm.*;
import jode.type.*;
import jode.util.SimpleMap;
import java.lang.reflect.InvocationTargetException;
import java.util.Hashtable;
import @COLLECTIONS@.Collections;
import @COLLECTIONS@.Collection;
import @COLLECTIONS@.Map;
import @COLLECTIONS@.Iterator;
import @COLLECTIONS@.Set;
public final class InvokeOperator extends Operator
implements MatchableOperator {
public final static int VIRTUAL = 0;
public final static int SPECIAL = 1;
public final static int STATIC = 2;
public final static int CONSTRUCTOR = 3;
public final static int ACCESSSPECIAL = 4;
/**
* The methodAnalyzer of the method, that contains this invocation.
* This is not the method that we should call.
*/
MethodAnalyzer methodAnalyzer;
int methodFlag;
MethodType methodType;
String methodName;
int skippedArgs;
Type classType;
Type[] hints;
/**
* This hashtable contains hints for every library method. Some
* library method take or return an int, but it should be a char
* instead. We will remember that here to give them the right
* hint.
*
* The key is the string: methodName + "." + methodType, the value
* is a map: It maps base class types for which this hint applies,
* to an array of hint types corresponding to the parameters: The
* first element is the hint type of the return value, the
* remaining entries are the hint types of the parameters. All
* hint types may be null, if that parameter shouldn't be hinted.
*/
private final static Hashtable hintTypes = new Hashtable();
static {
/* Fill the hint type hashtable. For example, the first
* parameter of String.indexOf should be hinted as char, even
* though the formal parameter is an int.
* First hint is hint of return value (even if void)
* other hints are that of the parameters in order
*
* You only have to hint the base class. Other classes will
* inherit the hints.
*
* We reuse a lot of objects, since they are all unchangeable
* this is no problem. We only hint for chars; it doesn't
* make much sense to hint for byte, since its constant
* representation is more difficult than an int
* representation. If you have more hints to suggest, please
* write contact me. (see GlobalOptions.EMAIL)
*/
Type tCharHint = new IntegerType(IntegerType.IT_I, IntegerType.IT_C);
Type[] hintC = new Type[] { tCharHint };
Type[] hint0C = new Type[] { null, tCharHint };
Type[] hint0C0 = new Type[] { null, tCharHint, null };
Map hintString0CMap = new SimpleMap
(Collections.singleton
(new SimpleMap.SimpleEntry(Type.tString, hint0C)));
Map hintString0C0Map = new SimpleMap
(Collections.singleton
(new SimpleMap.SimpleEntry(Type.tString, hint0C0)));
hintTypes.put("indexOf.(I)I", hintString0CMap);
hintTypes.put("lastIndexOf.(I)I", hintString0CMap);
hintTypes.put("indexOf.(II)I", hintString0C0Map);
hintTypes.put("lastIndexOf.(II)I", hintString0C0Map);
hintTypes.put("write.(I)V", new SimpleMap
(Collections.singleton
(new SimpleMap.SimpleEntry
(Type.tClass("java.io.Writer"), hint0C))));
hintTypes.put("read.()I", new SimpleMap
(Collections.singleton
(new SimpleMap.SimpleEntry
(Type.tClass("java.io.Reader"), hintC))));
hintTypes.put("unread.(I)V", new SimpleMap
(Collections.singleton
(new SimpleMap.SimpleEntry
(Type.tClass("java.io.PushbackReader"), hint0C))));
}
public InvokeOperator(MethodAnalyzer methodAnalyzer,
int methodFlag, Reference reference) {
super(Type.tUnknown, 0);
this.methodType = Type.tMethod(reference.getType());
this.methodName = reference.getName();
this.classType = Type.tType(reference.getClazz());
this.hints = null;
Map allHints = (Map) hintTypes.get(methodName+"."+methodType);
if (allHints != null) {
for (Iterator i = allHints.entrySet().iterator(); i.hasNext();) {
Map.Entry e = (Map.Entry) i.next();
if (classType.isOfType(((Type)e.getKey()).getSubType())) {
this.hints = (Type[]) e.getValue();
break;
}
}
}
if (hints != null && hints[0] != null)
this.type = hints[0];
else
this.type = methodType.getReturnType();
this.methodAnalyzer = methodAnalyzer;
this.methodFlag = methodFlag;
if (methodFlag == STATIC)
methodAnalyzer.useType(classType);
skippedArgs = (methodFlag == STATIC ? 0 : 1);
initOperands(skippedArgs + methodType.getParameterTypes().length);
checkAnonymousClasses();
}
public final boolean isStatic() {
return methodFlag == STATIC;
}
public MethodType getMethodType() {
return methodType;
}
public String getMethodName() {
return methodName;
}
public Type getClassType() {
return classType;
}
public int getPriority() {
return 950;
}
public void checkAnonymousClasses() {
if (methodFlag != CONSTRUCTOR
|| (Options.options & Options.OPTION_ANON) == 0)
return;
InnerClassInfo outer = getOuterClassInfo(getClassInfo());
if (outer != null && (outer.outer == null || outer.name == null)) {
methodAnalyzer.addAnonymousConstructor(this);
}
}
public void updateSubTypes() {
int offset = 0;
if (!isStatic()) {
subExpressions[offset++].setType(Type.tSubType(getClassType()));
}
Type[] paramTypes = methodType.getParameterTypes();
for (int i=0; i < paramTypes.length; i++) {
Type pType = (hints != null && hints[i+1] != null)
? hints[i+1] : paramTypes[i];
subExpressions[offset++].setType(Type.tSubType(pType));
}
}
public void updateType() {
}
/**
* Makes a non void expression out of this store instruction.
*/
public void makeNonVoid() {
if (type != Type.tVoid)
throw new jode.AssertError("already non void");
ClassInfo clazz = getClassInfo();
InnerClassInfo outer = getOuterClassInfo(clazz);
if (outer != null && outer.name == null) {
/* This is an anonymous class */
if (clazz.getInterfaces().length > 0)
type = Type.tClass(clazz.getInterfaces()[0]);
else
type = Type.tClass(clazz.getSuperclass());
} else
type = subExpressions[0].getType();
}
public boolean isConstructor() {
return methodFlag == CONSTRUCTOR;
}
public ClassInfo getClassInfo() {
if (classType instanceof ClassInterfacesType)
return ((ClassInterfacesType) classType).getClassInfo();
return null;
}
/**
* Checks, whether this is a call of a method from this class.
*/
public boolean isThis() {
return getClassInfo() == methodAnalyzer.getClazz();
}
public InnerClassInfo getOuterClassInfo(ClassInfo ci) {
if (ci != null) {
InnerClassInfo[] outers = ci.getOuterClasses();
if (outers != null)
return outers[0];
}
return null;
}
/**
* Tries to locate the class analyzer for the callee class. This
* is mainly useful for inner and anonymous classes.
*
* @return The class analyzer, if the callee class is declared
* inside the same base class as the caller class, null otherwise.
*/
public ClassAnalyzer getClassAnalyzer() {
if ((Options.options &
(Options.OPTION_ANON | Options.OPTION_INNER)) == 0)
return null;
ClassInfo callee = getClassInfo();
if (callee == null)
return null;
int nested = 0;
InnerClassInfo[] outers = callee.getOuterClasses();
if ((Options.options & Options.OPTION_INNER) != 0
&& outers != null) {
/* If the callee class is an inner class we take its
* (outermost) parent instead. This will assure that we
* find the callee class with one inner -> outer pass.
*/
nested = outers.length;
if (outers[nested - 1].outer == null
|| outers[nested - 1].name == null)
nested--;
if (nested > 0)
callee = ClassInfo.forName(outers[nested - 1].outer);
}
/* First check if it is an inner class */
ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer(callee);
if (ana == null) {
/* Now we iterate the caller analyzer queue to find the class
* analyzer for callee
*/
ana = methodAnalyzer.getClassAnalyzer();
while (callee != ana.getClazz()) {
if (ana.getParent() == null)
return null;
if (ana.getParent() instanceof MethodAnalyzer
&& (Options.options & Options.OPTION_ANON) != 0)
ana = ((MethodAnalyzer) ana.getParent())
.getClassAnalyzer();
else if (ana.getParent() instanceof ClassAnalyzer
&& (Options.options
& Options.OPTION_INNER) != 0)
ana = (ClassAnalyzer) ana.getParent();
else
throw new jode.AssertError
("Unknown parent: "+ana+": "+ana.getParent());
}
}
/* Now get the ClassAnalyzer of the real callee */
while (nested > 0) {
nested--;
ana = ana.getInnerClassAnalyzer(outers[nested].name);
if (ana == null)
return null;
}
return ana;
}
/**
* Checks, whether this is a call of a method from this class or an
* outer instance.
*/
public boolean isOuter() {
if (classType instanceof ClassInterfacesType) {
ClassInfo clazz = ((ClassInterfacesType) classType).getClassInfo();
ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer();
while (true) {
if (clazz == ana.getClazz())
return true;
if (ana.getParent() == null)
break;
if (ana.getParent() instanceof MethodAnalyzer
&& (Options.options & Options.OPTION_ANON) != 0)
ana = ((MethodAnalyzer) ana.getParent())
.getClassAnalyzer();
else if (ana.getParent() instanceof ClassAnalyzer
&& (Options.options
& Options.OPTION_INNER) != 0)
ana = (ClassAnalyzer) ana.getParent();
else
throw new jode.AssertError
("Unknown parent: "+ana+": "+ana.getParent());
}
}
return false;
}
/**
* Tries to locate the method analyzer for the callee. This
* is mainly useful for inner and anonymous classes.
*
* @return The method analyzer, if the callee is declared
* inside the same base class as the caller class, null otherwise.
*/
public MethodAnalyzer getMethodAnalyzer() {
ClassAnalyzer ana = getClassAnalyzer();
if (ana == null)
return null;
return ana.getMethod(methodName, methodType);
}
/**
* Checks, whether this is a call of a method from the super class.
* @XXX check, if its the first super class that implements the method.
*/
public boolean isSuperOrThis() {
ClassInfo clazz = getClassInfo();
if (clazz != null) {
return clazz.superClassOf(methodAnalyzer.getClazz());
}
return false;
}
public boolean isConstant() {
if ((Options.options & Options.OPTION_ANON) == 0)
return super.isConstant();
ClassInfo clazz = getClassInfo();
InnerClassInfo outer = getOuterClassInfo(clazz);
ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz);
if (clazzAna != null
&& outer != null && outer.outer == null && outer.name != null
&& clazzAna.getParent() == methodAnalyzer) {
/* This is a named method scope class, it needs
* declaration. And therefore can't be moved into
* a field initializer. */
return false;
}
return super.isConstant();
}
/**
* Checks if the value of the operator can be changed by this expression.
*/
public boolean matches(Operator loadop) {
return (loadop instanceof InvokeOperator
|| loadop instanceof GetFieldOperator);
}
/**
* Checks if the method is the magic class$ method.
* @return true if this is the magic class$ method, false otherwise.
*/
public boolean isGetClass() {
if (isThis()) {
SyntheticAnalyzer synth = getMethodAnalyzer().getSynthetic();
if (synth != null && synth.getKind() == SyntheticAnalyzer.GETCLASS)
return true;
}
return false;
}
class Environment extends SimpleRuntimeEnvironment {
Interpreter interpreter;
String classSig;
public Environment(String interpretedClassSig) {
classSig = interpretedClassSig.intern();
}
public Object invokeMethod(Reference ref, boolean isVirtual,
Object cls, Object[] params)
throws InterpreterException, InvocationTargetException {
if (cls == null && ref.getClazz().equals(classSig)) {
BytecodeInfo info =
ClassInfo.forName(ref.getClazz())
.findMethod(ref.getName(), ref.getType())
.getBytecode();
if (info != null)
return interpreter.interpretMethod(info, null, params);
throw new InterpreterException
("Can't interpret static native method: "+ref);
} else
return super.invokeMethod(ref, isVirtual, cls, params);
}
}
public ConstOperator deobfuscateString(ConstOperator op) {
ClassAnalyzer clazz = methodAnalyzer.getClassAnalyzer();
MethodAnalyzer ma = clazz.getMethod(methodName, methodType);
if (ma == null)
return null;
Environment env = new Environment("L"+methodAnalyzer.getClazz()
.getName().replace('.','/')+";");
Interpreter interpreter = new Interpreter(env);
env.interpreter = interpreter;
String result;
try {
result = (String) interpreter.interpretMethod
(ma.getBytecodeInfo(), null, new Object[] { op.getValue() });
} catch (InterpreterException ex) {
if ((GlobalOptions.debuggingFlags &
GlobalOptions.DEBUG_INTERPRT) != 0) {
GlobalOptions.err.println("Warning: Can't interpret method "
+methodName);
ex.printStackTrace(GlobalOptions.err);
}
return null;
} catch (InvocationTargetException ex) {
if ((GlobalOptions.debuggingFlags &
GlobalOptions.DEBUG_INTERPRT) != 0) {
GlobalOptions.err.println("Warning: Interpreted method throws"
+" an uncaught exception: ");
ex.getTargetException().printStackTrace(GlobalOptions.err);
}
return null;
}
return new ConstOperator(result);
}
public Expression simplifyStringBuffer() {
if (getClassType().equals(Type.tStringBuffer)) {
if (isConstructor()
&& subExpressions[0] instanceof NewOperator) {
if (methodType.getParameterTypes().length == 0)
return EMPTYSTRING;
if (methodType.getParameterTypes().length == 1
&& methodType.getParameterTypes()[0].equals(Type.tString))
return subExpressions[1].simplifyString();
}
if (!isStatic()
&& getMethodName().equals("append")
&& getMethodType().getParameterTypes().length == 1) {
Expression firstOp = subExpressions[0].simplifyStringBuffer();
if (firstOp == null)
return null;
subExpressions[1] = subExpressions[1].simplifyString();
if (firstOp == EMPTYSTRING
&& subExpressions[1].getType().isOfType(Type.tString))
return subExpressions[1];
if (firstOp instanceof StringAddOperator
&& (((Operator)firstOp).getSubExpressions()[0]
== EMPTYSTRING))
firstOp = ((Operator)firstOp).getSubExpressions()[1];
Expression secondOp = subExpressions[1];
Type[] paramTypes = new Type[] {
Type.tStringBuffer, secondOp.getType().getCanonic()
};
if (needsCast(1, paramTypes)) {
Type castType = methodType.getParameterTypes()[0];
Operator castOp = new ConvertOperator(castType, castType);
castOp.addOperand(secondOp);
secondOp = castOp;
}
Operator result = new StringAddOperator();
result.addOperand(secondOp);
result.addOperand(firstOp);
return result;
}
}
return null;
}
public Expression simplifyString() {
if (getMethodName().equals("toString")
&& !isStatic()
&& getClassType().equals(Type.tStringBuffer)
&& subExpressions.length == 1) {
Expression simple = subExpressions[0].simplifyStringBuffer();
if (simple != null)
return simple;
}
else if (getMethodName().equals("valueOf")
&& isStatic()
&& getClassType().equals(Type.tString)
&& subExpressions.length == 1) {
if (subExpressions[0].getType().isOfType(Type.tString))
return subExpressions[0];
Operator op = new StringAddOperator();
op.addOperand(subExpressions[0]);
op.addOperand(EMPTYSTRING);
}
/* The pizza way (pizza is the compiler of kaffe) */
else if (getMethodName().equals("concat")
&& !isStatic()
&& getClassType().equals(Type.tString)) {
Expression result = new StringAddOperator();
Expression right = subExpressions[1].simplify();
if (right instanceof StringAddOperator) {
Operator op = (Operator) right;
if (op.subExpressions != null
&& op.subExpressions[0] == EMPTYSTRING)
right = op.subExpressions[1];
}
result.addOperand(right);
result.addOperand(subExpressions[0].simplify());
}
else if ((Options.options & Options.OPTION_DECRYPT) != 0
&& isThis() && isStatic()
&& methodType.getParameterTypes().length == 1
&& methodType.getParameterTypes()[0].equals(Type.tString)
&& methodType.getReturnType().equals(Type.tString)) {
Expression expr = subExpressions[0].simplifyString();
if (expr instanceof ConstOperator) {
expr = deobfuscateString((ConstOperator)expr);
if (expr != null)
return expr;
}
}
return this;
}
public Expression simplifyAccess() {
if (getMethodAnalyzer() != null) {
SyntheticAnalyzer synth = getMethodAnalyzer().getSynthetic();
if (synth != null) {
Expression op = null;
switch (synth.getKind()) {
case SyntheticAnalyzer.ACCESSGETFIELD:
op = new GetFieldOperator(methodAnalyzer, false,
synth.getReference());
break;
case SyntheticAnalyzer.ACCESSGETSTATIC:
op = new GetFieldOperator(methodAnalyzer, true,
synth.getReference());
break;
case SyntheticAnalyzer.ACCESSPUTFIELD:
op = new StoreInstruction
(new PutFieldOperator(methodAnalyzer, false,
synth.getReference()));
break;
case SyntheticAnalyzer.ACCESSPUTSTATIC:
op = new StoreInstruction
(new PutFieldOperator(methodAnalyzer, true,
synth.getReference()));
break;
case SyntheticAnalyzer.ACCESSMETHOD:
op = new InvokeOperator(methodAnalyzer, ACCESSSPECIAL,
synth.getReference());
break;
case SyntheticAnalyzer.ACCESSSTATICMETHOD:
op = new InvokeOperator(methodAnalyzer, STATIC,
synth.getReference());
break;
case SyntheticAnalyzer.ACCESSCONSTRUCTOR:
if (subExpressions[1] instanceof ConstOperator
&& ((ConstOperator)
subExpressions[1]).getValue() == null) {
op = new InvokeOperator(methodAnalyzer, CONSTRUCTOR,
synth.getReference());
}
break;
}
if (op != null) {
if (subExpressions != null) {
for (int i=subExpressions.length; i-- > 0; ) {
if (i == 1 && synth.getKind()
== SyntheticAnalyzer.ACCESSCONSTRUCTOR)
// skip the null param.
continue;
op = op.addOperand(subExpressions[i]);
if (subExpressions[i].getFreeOperandCount() > 0)
break;
}
}
return op;
}
}
}
return null;
}
public boolean needsCast(int param, Type[] paramTypes) {
Type realClassType;
if (methodFlag == STATIC)
realClassType = classType;
else {
if (param == 0)
return paramTypes[0] instanceof NullType;
realClassType = paramTypes[0];
}
if (!(realClassType instanceof ClassInterfacesType)) {
/* Arrays don't have overloaded methods, all okay */
return false;
}
ClassInfo clazz = ((ClassInterfacesType) realClassType).getClassInfo();
int offset = skippedArgs;
Type[] myParamTypes = methodType.getParameterTypes();
if (myParamTypes[param-offset].equals(paramTypes[param])) {
/* Type at param is okay. */
return false;
}
/* Now check if there is a conflicting method in this class or
* a superclass. */
while (clazz != null) {
MethodInfo[] methods = clazz.getMethods();
next_method:
for (int i=0; i< methods.length; i++) {
if (!methods[i].getName().equals(methodName))
/* method name doesn't match*/
continue next_method;
Type[] otherParamTypes
= Type.tMethod(methods[i].getType()).getParameterTypes();
if (otherParamTypes.length != myParamTypes.length) {
/* parameter count doesn't match*/
continue next_method;
}
if (myParamTypes[param-offset].isOfType
(Type.tSubType(otherParamTypes[param-offset]))) {
/* cast to myParamTypes cannot resolve any conflicts. */
continue next_method;
}
for (int p = offset; p < paramTypes.length; p++) {
if (!paramTypes[p]
.isOfType(Type.tSubType(otherParamTypes[p-offset])))
/* No conflict here */
continue next_method;
}
/* There is a conflict that can be resolved by a cast. */
return true;
}
clazz = clazz.getSuperclass();
}
return false;
}
public Expression simplify() {
Expression expr = simplifyAccess();
if (expr != null)
return expr.simplify();
expr = simplifyString();
if (expr != this)
return expr.simplify();
return super.simplify();
}
/**
* We add the named method scoped classes to the declarables, and
* only fillDeclarables on the parameters we will print.
*/
public void fillDeclarables(Collection used) {
ClassInfo clazz = getClassInfo();
InnerClassInfo outer = getOuterClassInfo(clazz);
ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz);
if ((Options.options & Options.OPTION_ANON) != 0
&& outer != null && outer.outer == null && outer.name != null
&& clazzAna != null
&& clazzAna.getParent() == methodAnalyzer) {
/* This is a named method scope class, declare it.
* But first declare all method scoped classes,
* that are used inside; order does matter.
*/
clazzAna.fillDeclarables(used);
used.add(clazzAna);
}
if (!isConstructor() || isStatic()) {
super.fillDeclarables(used);
return;
}
int arg = 1;
int length = subExpressions.length;
boolean jikesAnonymousInner = false;
if ((Options.options & Options.OPTION_ANON) != 0
&& clazzAna != null
&& outer != null && (outer.outer == null || outer.name == null)) {
OuterValues ov = clazzAna.getOuterValues();
arg += ov.getCount();
jikesAnonymousInner = ov.isJikesAnonymousInner();
for (int i=1; i< arg; i++) {
Expression expr = subExpressions[i];
if (expr instanceof CheckNullOperator) {
CheckNullOperator cno = (CheckNullOperator) expr;
expr = cno.subExpressions[0];
}
expr.fillDeclarables(used);
}
if (outer.name == null) {
/* This is an anonymous class */
ClassInfo superClazz = clazz.getSuperclass();
ClassInfo[] interfaces = clazz.getInterfaces();
if (interfaces.length == 1
&& (superClazz == null
|| superClazz == ClassInfo.javaLangObject)) {
clazz = interfaces[0];
} else {
clazz = (superClazz != null
? superClazz : ClassInfo.javaLangObject);
}
outer = getOuterClassInfo(clazz);
}
}
if ((Options.options & Options.OPTION_INNER) != 0
&& outer != null && outer.outer != null && outer.name != null
&& !Modifier.isStatic(outer.modifiers)) {
Expression outerExpr = jikesAnonymousInner
? subExpressions[--length]
: subExpressions[arg++];
if (outerExpr instanceof CheckNullOperator) {
CheckNullOperator cno = (CheckNullOperator) outerExpr;
outerExpr = cno.subExpressions[0];
}
outerExpr.fillDeclarables(used);
}
for (int i=arg; i < length; i++)
subExpressions[i].fillDeclarables(used);
}
/**
* We add the named method scoped classes to the declarables, and
* only fillDeclarables on the parameters we will print.
*/
public void makeDeclaration(Set done) {
super.makeDeclaration(done);
if (isConstructor() && !isStatic()
&& (Options.options & Options.OPTION_ANON) != 0) {
ClassInfo clazz = getClassInfo();
InnerClassInfo outer = getOuterClassInfo(clazz);
ClassAnalyzer clazzAna = methodAnalyzer.getClassAnalyzer(clazz);
if (clazzAna != null && outer != null && outer.name == null) {
/* call makeDeclaration on the anonymous class, since
* _we_ will declare the anonymous class. */
clazzAna.makeDeclaration(done);
}
}
}
public int getBreakPenalty() {
return 5;
}
/* Invokes never equals: they may return different values even if
* they have the same parameters.
*/
public void dumpExpression(TabbedPrintWriter writer)
throws java.io.IOException {
int arg = 1;
int length = subExpressions.length;
boolean anonymousNew = false;
ClassInfo clazz = getClassInfo();
ClassAnalyzer clazzAna = null;
Type[] paramTypes = new Type[subExpressions.length];
for (int i=0; i< subExpressions.length; i++)
paramTypes[i] = subExpressions[i].getType().getCanonic();
writer.startOp(writer.NO_PAREN, 0);
switch (methodFlag) {
case CONSTRUCTOR: {
boolean qualifiedNew = false;
boolean jikesAnonymousInner = false;
/* Check if this is an anonymous constructor. In this case
* clazz and outer will be changed to point to the
* super class and anonymousNew will be set.
*/
InnerClassInfo outer = getOuterClassInfo(clazz);
if (outer != null && outer.name == null)
anonymousNew = true;
clazzAna = methodAnalyzer.getClassAnalyzer(clazz);
if ((~Options.options &
(Options.OPTION_ANON | Options.OPTION_CONTRAFO)) == 0
&& clazzAna != null
&& outer != null
&& (outer.outer == null || outer.name == null)) {
/* This is a method scoped class, skip the outerValues */
OuterValues ov = clazzAna.getOuterValues();
arg += ov.getCount();
jikesAnonymousInner = ov.isJikesAnonymousInner();
if (outer.name == null) {
/* This is an anonymous class */
ClassInfo superClazz = clazz.getSuperclass();
ClassInfo[] interfaces = clazz.getInterfaces();
if (interfaces.length == 1
&& (superClazz == null
|| superClazz == ClassInfo.javaLangObject)) {
clazz = interfaces[0];
} else {
if (interfaces.length > 0) {
writer.print("too many supers in ANONYMOUS ");
}
clazz = (superClazz != null
? superClazz : ClassInfo.javaLangObject);
}
outer = getOuterClassInfo(clazz);
if (jikesAnonymousInner && outer != null
&& outer.outer == null && outer.name != null) {
Expression thisExpr = subExpressions[--length];
if (thisExpr instanceof CheckNullOperator) {
CheckNullOperator cno
= (CheckNullOperator) thisExpr;
thisExpr = cno.subExpressions[0];
}
if (!(thisExpr instanceof ThisOperator)
|| (((ThisOperator) thisExpr).getClassInfo()
!= methodAnalyzer.getClazz()))
writer.print("ILLEGAL ANON CONSTR");
}
}
}
/* Check if this is an inner class. It will dump the outer
* class expression, except if its default.
*/
if (outer != null && outer.outer != null && outer.name != null
&& !Modifier.isStatic(outer.modifiers)
&& (~Options.options &
(Options.OPTION_INNER
| Options.OPTION_CONTRAFO)) == 0) {
Expression outerExpr = jikesAnonymousInner
? subExpressions[--length]
: subExpressions[arg++];
if (outerExpr instanceof CheckNullOperator) {
CheckNullOperator cno = (CheckNullOperator) outerExpr;
outerExpr = cno.subExpressions[0];
} else if (!(outerExpr instanceof ThisOperator)) {
if (!jikesAnonymousInner)
// Bug in jikes: it doesn't do a check null.
// We don't complain here.
writer.print("MISSING CHECKNULL ");
}
if (outerExpr instanceof ThisOperator) {
Scope scope = writer.getScope
(((ThisOperator) outerExpr).getClassInfo(),
Scope.CLASSSCOPE);
if (writer.conflicts(outer.name, scope, Scope.CLASSNAME)) {
qualifiedNew = true;
outerExpr.dumpExpression(writer, 950);
writer.breakOp();
writer.print(".");
}
} else {
qualifiedNew = true;
if (outerExpr.getType() instanceof NullType) {
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 1);
writer.print("(");
writer.printType(Type.tClass
(ClassInfo.forName(outer.outer)));
writer.print(") ");
writer.breakOp();
outerExpr.dumpExpression(writer, 700);
writer.endOp();
writer.print(")");
} else
outerExpr.dumpExpression(writer, 950);
writer.breakOp();
writer.print(".");
}
}
if (subExpressions[0] instanceof NewOperator
&& paramTypes[0].equals(classType)) {
writer.print("new ");
if (qualifiedNew)
writer.print(outer.name);
else
writer.printType(Type.tClass(clazz));
break;
}
if (subExpressions[0] instanceof ThisOperator
&& (((ThisOperator)subExpressions[0]).getClassInfo()
== methodAnalyzer.getClazz())) {
if (isThis())
writer.print("this");
else
writer.print("super");
break;
}
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 0);
writer.print("(UNCONSTRUCTED)");
writer.breakOp();
subExpressions[0].dumpExpression(writer, 700);
writer.endOp();
writer.print(")");
writer.breakOp();
writer.print(".");
writer.printType(Type.tClass(clazz));
break;
}
case SPECIAL:
if (isSuperOrThis()) {
if (!isThis()) {
/* We don't have to check if this is the real super
* class, as long as ACC_SUPER is set.
*/
writer.print("super");
ClassInfo superClazz = getClassInfo().getSuperclass();
paramTypes[0] = superClazz == null
? Type.tObject : Type.tClass(superClazz);
writer.breakOp();
writer.print(".");
} else {
/* XXX check if this is a private method. */
}
} else {
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 0);
writer.print("(NON VIRTUAL ");
writer.printType(classType);
writer.print(") ");
writer.breakOp();
subExpressions[0].dumpExpression(writer, 700);
writer.endOp();
writer.print(")");
writer.breakOp();
writer.print(".");
}
writer.print(methodName);
break;
case ACCESSSPECIAL:
/* Calling a private method in another class. (This is
* allowed for inner classes.)
*/
if (paramTypes[0].equals(classType))
subExpressions[0].dumpExpression(writer, 950);
else {
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 0);
writer.print("(");
writer.printType(classType);
writer.print(") ");
writer.breakOp();
paramTypes[0] = classType;
subExpressions[0].dumpExpression(writer, 700);
writer.endOp();
writer.print(")");
}
writer.breakOp();
writer.print(".");
writer.print(methodName);
break;
case STATIC: {
arg = 0;
Scope scope = writer.getScope(getClassInfo(),
Scope.CLASSSCOPE);
if (scope == null
||writer.conflicts(methodName, scope, Scope.METHODNAME)) {
writer.printType(classType);
writer.breakOp();
writer.print(".");
}
writer.print(methodName);
break;
}
case VIRTUAL:
if (subExpressions[0] instanceof ThisOperator) {
ThisOperator thisOp = (ThisOperator) subExpressions[0];
Scope scope = writer.getScope(thisOp.getClassInfo(),
Scope.CLASSSCOPE);
if (writer.conflicts(methodName, scope, Scope.METHODNAME)) {
thisOp.dumpExpression(writer, 950);
writer.breakOp();
writer.print(".");
} else if (/* This is a inherited field conflicting
* with a field name in some outer class.
*/
getMethodAnalyzer() == null
&& writer.conflicts(methodName, null,
Scope.NOSUPERMETHODNAME)) {
ClassAnalyzer ana = methodAnalyzer.getClassAnalyzer();
while (ana.getParent() instanceof ClassAnalyzer
&& ana != scope)
ana = (ClassAnalyzer) ana.getParent();
if (ana == scope) {
// For a simple outer class we can say this
writer.print("this");
} else {
// For a class that owns a method that owns
// us, we have to give the full class name
thisOp.dumpExpression(writer, 950);
}
writer.breakOp();
writer.print(".");
}
} else {
if (needsCast(0, paramTypes)){
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 1);
writer.print("(");
writer.printType(classType);
writer.print(") ");
writer.breakOp();
subExpressions[0].dumpExpression(writer, 700);
writer.endOp();
writer.print(")");
paramTypes[0] = classType;
} else
subExpressions[0].dumpExpression(writer, 950);
writer.breakOp();
writer.print(".");
}
writer.print(methodName);
}
writer.endOp();
writer.breakOp();
writer.print("(");
writer.startOp(writer.EXPL_PAREN, 0);
boolean first = true;
int offset = skippedArgs;
while (arg < length) {
if (!first) {
writer.print(", ");
writer.breakOp();
} else
first = false;
int priority = 0;
if (needsCast(arg, paramTypes)) {
Type castType = methodType.getParameterTypes()[arg-offset];
writer.startOp(writer.IMPL_PAREN, 1);
writer.print("(");
writer.printType(castType);
writer.print(") ");
writer.breakOp();
paramTypes[arg] = castType;
priority = 700;
}
subExpressions[arg++].dumpExpression(writer, priority);
if (priority == 700)
writer.endOp();
}
writer.endOp();
writer.print(")");
if (anonymousNew) {
/* If this was an anonymous constructor call, we must now
* dump the source code of the anonymous class.
*/
Object state = writer.saveOps();
writer.openBrace();
writer.tab();
clazzAna.dumpBlock(writer);
writer.untab();
writer.closeBraceNoSpace();
writer.restoreOps(state);
}
}
public boolean opEquals(Operator o) {
if (o instanceof InvokeOperator) {
InvokeOperator i = (InvokeOperator)o;
return classType.equals(i.classType)
&& methodName.equals(i.methodName)
&& methodType.equals(i.methodType)
&& methodFlag == i.methodFlag;
}
return false;
}
}