/* Soot - a J*va Optimization Framework * Copyright (C) 1997-1999 Raja Vallee-Rai * Copyright (C) 2004 Ondrej Lhotak * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the Sable Research Group and others 1997-1999. * See the 'credits' file distributed with Soot for the complete list of * contributors. (Soot is distributed at http://www.sable.mcgill.ca/soot) */ package soot; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import soot.jimple.toolkits.callgraph.VirtualCalls; import soot.tagkit.AbstractHost; import soot.util.IterableSet; import soot.util.Numberable; import soot.util.NumberedString; /** Soot representation of a Java method. Can be declared to belong to a SootClass. Does not contain the actual code, which belongs to a Body. The getActiveBody() method points to the currently-active body. */ public class SootMethod extends AbstractHost implements ClassMember, Numberable, MethodOrMethodContext { public static final String constructorName = "<init>"; public static final String staticInitializerName = "<clinit>"; public static boolean DEBUG=false; /** Name of the current method. */ String name; /** A list of parameter types taken by this <code>SootMethod</code> object, * in declaration order. */ List parameterTypes; /** The return type of this object. */ Type returnType; /** True when some <code>SootClass</code> object declares this <code>SootMethod</code> object. */ boolean isDeclared; /** Holds the class which declares this <code>SootClass</code> method. */ SootClass declaringClass; /** Modifiers associated with this SootMethod (e.g. private, protected, etc.) */ int modifiers; /** Is this method a phantom method? */ boolean isPhantom = false; /** Declared exceptions thrown by this method. Created upon demand. */ List<SootClass> exceptions = null; /** Active body associated with this method. */ Body activeBody; /** Tells this method how to find out where its body lives. */ protected MethodSource ms; /** Uses methodSource to retrieve the method body in question; does not set it * to be the active body. * * @param phaseName Phase name for body loading. */ private Body getBodyFromMethodSource(String phaseName) { return ms.getBody(this, phaseName); } /** Sets the MethodSource of the current SootMethod. */ public void setSource(MethodSource ms) { this.ms = ms; } /** Returns the MethodSource of the current SootMethod. */ public MethodSource getSource() { return ms; } /** Returns a hash code for this method consistent with structural equality. */ public int equivHashCode() { return returnType.hashCode() * 101 + modifiers * 17 + name.hashCode(); } /** Constructs a SootMethod with the given name, parameter types and return type. */ public SootMethod(String name, List parameterTypes, Type returnType) { this(name, parameterTypes, returnType, 0, Collections.<SootClass>emptyList()); } /** Constructs a SootMethod with the given name, parameter types, return type and modifiers. */ public SootMethod( String name, List parameterTypes, Type returnType, int modifiers) { this(name, parameterTypes, returnType, modifiers, Collections.<SootClass>emptyList()); } /** Constructs a SootMethod with the given name, parameter types, return type, * and list of thrown exceptions. */ public SootMethod( String name, List parameterTypes, Type returnType, int modifiers, List<SootClass> thrownExceptions) { this.name = name; this.parameterTypes = new ArrayList(); this.parameterTypes.addAll(parameterTypes); this.parameterTypes = Collections.unmodifiableList(this.parameterTypes); this.returnType = returnType; this.modifiers = modifiers; if (exceptions == null && !thrownExceptions.isEmpty()) { exceptions = new ArrayList<SootClass>(); this.exceptions.addAll(thrownExceptions); /*DEBUG=true; if(DEBUG) System.out.println("Added thrown exceptions"+thrownExceptions); DEBUG=false; */ } Scene.v().getMethodNumberer().add(this); subsignature = Scene.v().getSubSigNumberer().findOrAdd(getSubSignature()); } /** Returns the name of this method. */ public String getName() { return name; } /** Nomair A. Naeem , January 14th 2006 * Need it for the decompiler to create a new SootMethod * The SootMethod can be created fine but when one tries to create a SootMethodRef there is an error because * there is no declaring class set. Dava cannot add the method to the class until after it has ended decompiling * the remaining method (new method added is added in the PackManager) * It would make sense to setDeclared to true within this method too. However later when the sootMethod is added it checks * that the method is not set to declared (isDeclared). */ public void setDeclaringClass(SootClass declClass){ if(declClass != null){ declaringClass=declClass; //setDeclared(true); } } /** Returns the class which declares the current <code>SootMethod</code>. */ public SootClass getDeclaringClass() { if (!isDeclared) throw new RuntimeException("not declared: " + getName()); return declaringClass; } public void setDeclared(boolean isDeclared) { this.isDeclared = isDeclared; } /** Returns true when some <code>SootClass</code> object declares this <code>SootMethod</code> object. */ public boolean isDeclared() { return isDeclared; } /** Returns true when this <code>SootMethod</code> object is phantom. */ public boolean isPhantom() { return isPhantom; } /** * Returns true if this method is not phantom, abstract or native, i.e. this method can have a body. */ public boolean isConcrete() { return !isPhantom() && !isAbstract() && !isNative(); } /** Sets the phantom flag on this method. */ public void setPhantom(boolean value) { if (value) { if (!Scene.v().allowsPhantomRefs()) throw new RuntimeException("Phantom refs not allowed"); if (declaringClass != null && !declaringClass.isPhantom()) throw new RuntimeException("Declaring class would have to be phantom"); } isPhantom = value; } /** Sets the name of this method. */ public void setName(String name) { boolean wasDeclared = isDeclared; SootClass oldDeclaringClass = declaringClass; if( wasDeclared ) oldDeclaringClass.removeMethod(this); this.name = name; subsignature = Scene.v().getSubSigNumberer().findOrAdd(getSubSignature()); if( wasDeclared) oldDeclaringClass.addMethod(this); } /** Gets the modifiers of this method. * @see soot.Modifier */ public int getModifiers() { return modifiers; } /** Sets the modifiers of this method. * @see soot.Modifier */ public void setModifiers(int modifiers) { // RoboVM note: We allow all classes to be modified // if ((declaringClass != null) && (!declaringClass.isApplicationClass())) // throw new RuntimeException("Cannot set modifiers of a method from a non-app class!"); this.modifiers = modifiers; } /** Returns the return type of this method. */ public Type getReturnType() { return returnType; } /** Sets the return type of this method. */ public void setReturnType(Type t) { boolean wasDeclared = isDeclared; SootClass oldDeclaringClass = declaringClass; if( wasDeclared ) oldDeclaringClass.removeMethod(this); returnType = t; subsignature = Scene.v().getSubSigNumberer().findOrAdd(getSubSignature()); if( wasDeclared) oldDeclaringClass.addMethod(this); } /** Returns the number of parameters taken by this method. */ public int getParameterCount() { return parameterTypes.size(); } /** Gets the type of the <i>n</i>th parameter of this method. */ public Type getParameterType(int n) { return (Type) parameterTypes.get(n); } /** * Returns a read-only list of the parameter types of this method. */ public List getParameterTypes() { return parameterTypes; } /** * Changes the set of parameter types of this method. */ public void setParameterTypes( List l ) { boolean wasDeclared = isDeclared; SootClass oldDeclaringClass = declaringClass; if( wasDeclared ) oldDeclaringClass.removeMethod(this); List al = new ArrayList(); al.addAll(l); this.parameterTypes = Collections.unmodifiableList(al); subsignature = Scene.v().getSubSigNumberer().findOrAdd(getSubSignature()); if( wasDeclared) oldDeclaringClass.addMethod(this); } /** Retrieves the active body for this method. */ public Body getActiveBody() { if (declaringClass!=null && declaringClass.isPhantomClass()) throw new RuntimeException( "cannot get active body for phantom class: " + getSignature()); // ignore empty body exceptions if we are just computing coffi metrics if (!hasActiveBody()) throw new RuntimeException( "no active body present for method " + getSignature()); return activeBody; } /** * Returns the active body if present, else constructs an active body and returns that. * * If you called Scene.v().loadClassAndSupport() for a class yourself, it will * not be an application class, so you cannot get retrieve its active body. * Please call setApplicationClass() on the relevant class. */ public Body retrieveActiveBody() { declaringClass.checkLevel(SootClass.BODIES); if (declaringClass.isPhantomClass()) throw new RuntimeException( "cannot get resident body for phantom class : " + getSignature() + "; maybe you want to call c.setApplicationClass() on this class!"); if (!hasActiveBody()) { // G.v().out.println("Retrieving "+this.getSignature()); setActiveBody(this.getBodyFromMethodSource("jb")); ms = null; } return getActiveBody(); } /** Sets the active body for this method. */ public void setActiveBody(Body body) { if ((declaringClass != null) && declaringClass.isPhantomClass()) throw new RuntimeException( "cannot set active body for phantom class! " + this); if (!isConcrete()) throw new RuntimeException( "cannot set body for non-concrete method! " + this); if (body!= null && body.getMethod() != this) body.setMethod(this); activeBody = body; } /** Returns true if this method has an active body. */ public boolean hasActiveBody() { return activeBody != null; } /** Releases the active body associated with this method. */ public void releaseActiveBody() { activeBody = null; } /** Adds the given exception to the list of exceptions thrown by this method * unless the exception is already in the list. */ public void addExceptionIfAbsent(SootClass e) { if( !throwsException(e) ) addException(e); } /** Adds the given exception to the list of exceptions thrown by this method. */ public void addException(SootClass e) { if(DEBUG) System.out.println("Adding exception "+e); if (exceptions == null) exceptions = new ArrayList<SootClass>(); else if (exceptions.contains(e)) throw new RuntimeException( "already throws exception " + e.getName()); exceptions.add(e); } /** Removes the given exception from the list of exceptions thrown by this method. */ public void removeException(SootClass e) { if(DEBUG) System.out.println("Removing exception "+e); if (exceptions == null) exceptions = new ArrayList<SootClass>(); if (!exceptions.contains(e)) throw new RuntimeException( "does not throw exception " + e.getName()); exceptions.remove(e); } /** Returns true if this method throws exception <code>e</code>. */ public boolean throwsException(SootClass e) { return exceptions != null && exceptions.contains(e); } public void setExceptions(List exceptions) { this.exceptions = new ArrayList<SootClass>(); this.exceptions.addAll(exceptions); } /** * Returns a backed list of the exceptions thrown by this method. */ public List<SootClass> getExceptions() { if (exceptions == null) exceptions = new ArrayList<SootClass>(); return exceptions; } /** * Convenience method returning true if this method is static. */ public boolean isStatic() { return Modifier.isStatic(this.getModifiers()); } /** * Convenience method returning true if this method is private. */ public boolean isPrivate() { return Modifier.isPrivate(this.getModifiers()); } /** * Convenience method returning true if this method is public. */ public boolean isPublic() { return Modifier.isPublic(this.getModifiers()); } /** * Convenience method returning true if this method is protected. */ public boolean isProtected() { return Modifier.isProtected(this.getModifiers()); } /** * Convenience method returning true if this method is abstract. */ public boolean isAbstract() { return Modifier.isAbstract(this.getModifiers()); } /** * Convenience method returning true if this method is native. */ public boolean isNative() { return Modifier.isNative(this.getModifiers()); } /** * Convenience method returning true if this method is synchronized. */ public boolean isSynchronized() { return Modifier.isSynchronized(this.getModifiers()); } /** * * @return yes if this is the main method */ public boolean isMain() { if ( isPublic() && isStatic() ) { NumberedString main_sig = Scene.v().getSubSigNumberer().findOrAdd( "void main(java.lang.String[])" ); if ( main_sig.equals( subsignature ) ) return true; } return false; } /** * * @return yes, if this function is a constructor. */ public boolean isConstructor() { return name.equals(constructorName); } /** * @return yes, if this is a class initializer or main function. */ public boolean isEntryMethod() { if ( isStatic() && subsignature.equals( VirtualCalls.v().sigClinit ) ) return true; return isMain(); } /** * We rely on the JDK class recognition to decide if a method is JDK method. */ public boolean isJavaLibraryMethod() { SootClass cl = getDeclaringClass(); return cl.isJavaLibraryClass(); } /** Returns the Soot signature of this method. Used to refer to methods unambiguously. */ public String getSignature() { return getSignature(getDeclaringClass(), getName(), getParameterTypes(), getReturnType()); } public static String getSignature(SootClass cl, String name, List params, Type returnType) { StringBuffer buffer = new StringBuffer(); buffer.append( "<" + Scene.v().quotedNameOf(cl.getName()) + ": "); buffer.append(getSubSignatureImpl(name, params, returnType)); buffer.append(">"); // Again, memory-usage tweak depending on JDK implementation due // to Michael Pan. return buffer.toString().intern(); } /** Returns the Soot subsignature of this method. Used to refer to methods unambiguously. */ public String getSubSignature() { String name = getName(); List params = getParameterTypes(); Type returnType = getReturnType(); return getSubSignatureImpl(name, params, returnType); } public static String getSubSignature( String name, List params, Type returnType) { return getSubSignatureImpl(name, params, returnType); } private static String getSubSignatureImpl( String name, List params, Type returnType) { StringBuffer buffer = new StringBuffer(); Type t = returnType; // RoboVM note: Optimization //buffer.append(t.toString() + " " + Scene.v().quotedNameOf(name) + "("); buffer.append(t.toString()); buffer.append(' '); buffer.append(Scene.v().quotedNameOf(name) + "("); Iterator typeIt = params.iterator(); if (typeIt.hasNext()) { t = (Type) typeIt.next(); buffer.append(t); while (typeIt.hasNext()) { buffer.append(","); t = (Type) typeIt.next(); buffer.append(t); } } buffer.append(")"); // RoboVM note: Optimization //return buffer.toString().intern(); return buffer.toString(); } private NumberedString subsignature; public NumberedString getNumberedSubSignature() { return subsignature; } /** Returns the signature of this method. */ public String toString() { return getSignature(); } /** * Returns the declaration of this method, as used at the top of textual body representations * (before the {}'s containing the code for representation.) */ public String getDeclaration() { StringBuffer buffer = new StringBuffer(); // modifiers StringTokenizer st = new StringTokenizer(Modifier.toString(this.getModifiers())); if (st.hasMoreTokens()) buffer.append(st.nextToken()); // RoboVM note: optimization // while (st.hasMoreTokens()) // buffer.append(" " + st.nextToken()); // // if (buffer.length() != 0) // buffer.append(" "); while (st.hasMoreTokens()) buffer.append(' ').append(st.nextToken()); if (buffer.length() != 0) buffer.append(' '); // return type + name // RoboVM note: optimization // buffer.append(this.getReturnType() + " "); // buffer.append(Scene.v().quotedNameOf(this.getName())); // // buffer.append("("); buffer.append(this.getReturnType()).append(' '); buffer.append(Scene.v().quotedNameOf(this.getName())); buffer.append('('); // parameters Iterator typeIt = this.getParameterTypes().iterator(); //int count = 0; while (typeIt.hasNext()) { Type t = (Type) typeIt.next(); buffer.append(t); if (typeIt.hasNext()) buffer.append(", "); } buffer.append(")"); // Print exceptions if (exceptions != null) { Iterator<SootClass> exceptionIt = this.getExceptions().iterator(); // RoboVM note: optimization // if (exceptionIt.hasNext()) { // buffer.append( // " throws " + exceptionIt.next().getName()); // // while (exceptionIt.hasNext()) { // buffer.append( // ", " + exceptionIt.next().getName()); // } // } if (exceptionIt.hasNext()) { buffer.append( " throws ").append(exceptionIt.next().getName()); while (exceptionIt.hasNext()) { buffer.append( ", ").append(exceptionIt.next().getName()); } } } // RoboVM note: Optimization //return buffer.toString().intern(); return buffer.toString().intern(); } public final int getNumber() { return number; } public final void setNumber(int number) { this.number = number; } private int number = 0; public SootMethod method() { return this; } public Context context() { return null; } public SootMethodRef makeRef() { return Scene.v().makeMethodRef( declaringClass, name, parameterTypes, returnType, isStatic() ); } }