/* 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) { GlobalOptions.err.println("Warning: Can't interpret method " +methodName); ex.printStackTrace(GlobalOptions.err); return null; } catch (InvocationTargetException ex) { 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); } } } /* 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(); 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.print("."); } } else { qualifiedNew = true; if (outerExpr.getType() instanceof NullType) { writer.print("(("); writer.printType(Type.tClass (ClassInfo.forName(outer.outer))); writer.print(") "); outerExpr.dumpExpression(writer, 700); writer.print(")"); } else outerExpr.dumpExpression(writer, 950); 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("((UNCONSTRUCTED)"); subExpressions[0].dumpExpression(writer, 950); 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.print("."); } else { /* XXX check if this is a private method. */ } } else { writer.print("((NON VIRTUAL "); writer.printType(classType); writer.print(")"); paramTypes[0] = classType; subExpressions[0].dumpExpression(writer, 700); 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.printType(classType); writer.print(")"); paramTypes[0] = classType; subExpressions[0].dumpExpression(writer, 700); writer.print(")"); } 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.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.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.print("."); } } } else { if (needsCast(0, paramTypes)){ writer.print("(("); writer.printType(classType); writer.print(") "); subExpressions[0].dumpExpression(writer, 700); writer.print(")"); paramTypes[0] = classType; } else subExpressions[0].dumpExpression(writer, 950); writer.print("."); } writer.print(methodName); } writer.print("("); boolean first = true; int offset = skippedArgs; while (arg < length) { if (!first) writer.print(", "); else first = false; int priority = 0; if (needsCast(arg, paramTypes)) { Type castType = methodType.getParameterTypes()[arg-offset]; writer.print("("); writer.printType(castType); writer.print(") "); paramTypes[arg] = castType; priority = 700; } subExpressions[arg++].dumpExpression(writer, priority); } writer.print(")"); if (anonymousNew) { /* If this was an anonymous constructor call, we must now * dump the source code of the anonymous class. */ writer.openBrace(); writer.tab(); clazzAna.dumpBlock(writer); writer.untab(); writer.closeBraceNoSpace(); } } 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; } }