/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2010, Stefan Hepp (stefan@stefant.org). * * 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common; import com.jopdesign.common.bcel.ParameterAnnotationAttribute; import com.jopdesign.common.code.CallString; import com.jopdesign.common.graphutils.ClassHierarchyTraverser; import com.jopdesign.common.graphutils.ClassVisitor; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.AppInfoError; import com.jopdesign.common.misc.JavaClassFormatError; import com.jopdesign.common.misc.Ternary; import com.jopdesign.common.type.MemberID; import com.jopdesign.common.type.MethodRef; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.Type; import org.apache.log4j.Logger; import java.util.Collection; import java.util.LinkedList; import java.util.List; /** * @author Stefan Hepp (stefan@stefant.org) */ public final class MethodInfo extends ClassMemberInfo { private final MethodGen methodGen; private MethodCode methodCode; private static final Logger logger = Logger.getLogger(LogConfig.LOG_STRUCT+".MethodInfo"); public MethodInfo(ClassInfo classInfo, MethodGen methodGen) { super(classInfo, new MemberID(classInfo.getClassName(), methodGen.getName(), methodGen.getSignature()), methodGen); this.methodGen = methodGen; updateMethodCode(); } ////////////////////////////////////////////////////////////////////////////// // Lots of wrapper stuff for MethodGen. // You do not get direct access to my private methodGen. Nix. ////////////////////////////////////////////////////////////////////////////// public boolean isAbstract() { return methodGen.isAbstract(); } public void setAbstract(boolean val) { methodGen.isAbstract(val); updateMethodCode(); } public boolean isSynchronized() { return methodGen.isSynchronized(); } public void setSynchronized(boolean val) { methodGen.isSynchronized(val); } public boolean isNative() { return methodGen.isNative(); } public void setNative(boolean val) { methodGen.isNative(val); updateMethodCode(); } public boolean isStrictFP() { return methodGen.isStrictfp(); } public void setStrictFP(boolean val) { methodGen.isStrictfp(val); } public String[] getArgumentNames() { return methodGen.getArgumentNames(); } public void setArgumentNames(String[] arg_names) { methodGen.setArgumentNames(arg_names); } public void setArgumentName(int i, String name) { methodGen.setArgumentName(i, name); } public Type[] getArgumentTypes() { return methodGen.getArgumentTypes(); } public Type getArgumentType(int i) { return methodGen.getArgumentType(i); } public ParameterAnnotationAttribute getParameterAnnotation(boolean visible) { for (Attribute a : getAttributes()) { if ( a instanceof ParameterAnnotationAttribute ) { if ( ((ParameterAnnotationAttribute)a).isVisible() == visible ) { return (ParameterAnnotationAttribute) a; } } } return null; } ////////////////////////////////////////////////////////////////////////////// // Code access and Control Flow Graph stuff ////////////////////////////////////////////////////////////////////////////// /** * @return true if this method is neither abstract nor native. */ public boolean hasCode() { return methodCode != null; } public MethodCode getCode() { return methodCode; } /** * Compile the code and return a new BCEL method. * <p> * This function should also clean up internal resources of MethodInfo and MethodCode. * </p> * @see MethodCode#compile() * @return a new BCEL method class containing all changes to the code. */ public Method compile() { if (hasCode()) { methodCode.compile(); } // TODO: to reduce memory consumption, dispose instruction handlers and compile to Method instead of MethodGen? return methodGen.getMethod(); } /** * Get a BCEL method for this methodInfo. * * @param compile if true, this does the same as {@link #compile()}. * @return a method for this methodInfo. */ public Method getMethod(boolean compile) { if ( compile ) { // we use the compile flag primarily as a reminder to the API user to compile first return compile(); } return methodGen.getMethod(); } ////////////////////////////////////////////////////////////////////////////// // Interface implementations, name and signature ////////////////////////////////////////////////////////////////////////////// /** * This is the same as {@link #getMemberID()}.toString(). * @return classname and method signature of this method. */ public String getFQMethodName() { return getClassInfo().getClassName() + "." + getMethodSignature(); } public MethodRef getMethodRef() { return new MethodRef(this); } /** * Get the signature of this method (i.e. its simple name and the descriptor). * @return the signature of this method without the class part. */ public String getMethodSignature() { return methodGen.getName() + methodGen.getSignature(); } ////////////////////////////////////////////////////////////////////////////// // Helper methods to find implementations and super methods ////////////////////////////////////////////////////////////////////////////// /** * Check if this method is the same as or overrides a given method. * * @param superMethod the superMethod to check. * @param checkSignature if true, check if the given method has the same signature and is defined in a subclass * of this method's class. * @return true if this method overrides the given method and can access the method. */ public boolean overrides(MethodInfo superMethod, boolean checkSignature) { return overrides(superMethod.getMethodRef(), checkSignature); } /** * @param interfaceMethod A method within an interface. * @return true if this method implements the interface method, even if the class does not implement the interface. */ public boolean implementsMethod(MethodRef interfaceMethod) { if (interfaceMethod.isInterfaceMethod() != Ternary.TRUE) return false; if (!getMethodSignature().equals(interfaceMethod.getMethodSignature())) { return false; } // no need for access checks, interfaces are always public. return true; } /** * Check if this method is the same as or overrides a given method. * <p> * This checks the class of the reference if checkSignature is true, so even if the reference resolves * to this method, this returns false if the reference refers to a subclass of this method's class. * </p> * <p>This might not work as expected for interface methods. To check if this method implements * an interface method even if the class of this method does not implement the interface, use * {@link #implementsMethod(MethodRef)} instead.</p> * * @param superMethod the superMethod to check, must refer to a known class. * @param checkSignature if true, check if the given method has the same signature and if the reference refers to * a superclass of this method's class. If this is false, it is assumed that the signatures match and this * method's class is a subclass of the referred class. * @return true if this method overrides the given method and can access the method. */ public boolean overrides(MethodRef superMethod, boolean checkSignature) { ClassInfo superClass = superMethod.getClassInfo(); if (superClass == null) { // No need to check if the classname is equal to this method's class, in this case we would have a ClassInfo throw new AppInfoError("Trying to lookup unknown class for " + superMethod+", not supported."); } if (superClass.equals(getClassInfo())) { // refers to same class.. Must be the same method if the signature matches if ( checkSignature && !getMethodSignature().equals(superMethod.getMethodSignature()) ) { return false; } return true; } // A static method may hide a static or instance method, but does not override it. if ( isStatic() ) { return false; } MethodInfo sm = superMethod.getMethodInfo(); // special case: check if this method is the method which is inherited to the referenced class if ( this.equals(sm) ) { return true; } if (checkSignature) { if ( !getMethodSignature().equals(superMethod.getMethodSignature()) ) { return false; } if ( !getClassInfo().isSubclassOf(superClass) ) { return false; } } if (sm == null) { throw new AppInfoError("Trying to check unknown method "+superMethod+", this is not supported."); } if ( sm.isStatic() ) { logger.warn("Instance method " + getMemberID()+" overrides static method "+sm.getMemberID()); } return getClassInfo().canAccess(sm); } /** * Get the super method for this method, if there is any. * * @param nonAbstractOnly if true, return the method from the lowest superclass * @param checkAccess if false, also return hidden methods and methods which cannot be accessed by this method's class. * @return the super method or null if none found. */ public MethodInfo getSuperMethod(boolean nonAbstractOnly, boolean checkAccess) { if ( checkAccess && (isPrivate() || isStatic()) ) { return null; } ClassInfo superClass = getClassInfo().getSuperClassInfo(); if ( superClass != null ) { MethodInfo inherited = superClass.getMethodInfoInherited(getMemberID(), checkAccess); return checkAccess || overrides(inherited, false) ? inherited : null; } else { return null; } } /** * Get all methods definitions from all interfaces implemented by this method. * * @return a list of all interface methods implemented by this method. */ public Collection<MethodInfo> getInterfaceMethods() { final List<MethodInfo> ifMethods = new LinkedList<MethodInfo>(); ClassVisitor visitor = new ClassVisitor() { public boolean visitClass(ClassInfo classInfo) { if ( !classInfo.isInterface() ) { return false; } MethodInfo ifMethod = classInfo.getMethodInfo(getMethodSignature()); if ( ifMethod != null ) { ifMethods.add(ifMethod); } return true; } public void finishClass(ClassInfo classInfo) { } }; new ClassHierarchyTraverser(visitor).traverseUp(getClassInfo()); return ifMethods; } /** * Find all methods which override/implement this method. * Instance method are overridden by other instance methods and hidden by static methods. * * @param checkAccess if false, find methods with the same signature in subclasses even if they * do not override this method (i.e. private or static methods). * @return a list of all overriding methods. */ public Collection<MethodInfo> getOverriders(final boolean checkAccess) { final List<MethodInfo> overriders = new LinkedList<MethodInfo>(); if (checkAccess && (isPrivate() || isStatic())) { return overriders; } ClassVisitor visitor = new ClassVisitor() { public boolean visitClass(ClassInfo classInfo) { MethodInfo overrider = classInfo.getMethodInfo(getMethodSignature()); if ( overrider != null ) { if ( overrider.isPrivate() ) { // found an overriding method which is private .. this is interesting.. logger.error("Found private method "+overrider.getMethodSignature()+" in "+ classInfo.getClassName()+" overriding non-private method in "+ getClassInfo().getClassName()); } // If a subclass contains a static method or a package visible method with same // signature, but is in a different package, it does NOT override this method. if ( !checkAccess || overrider.overrides(MethodInfo.this, false) ) { overriders.add(overrider); } } return true; } public void finishClass(ClassInfo classInfo) { } }; new ClassHierarchyTraverser(visitor).traverseDown(getClassInfo()); return overriders; } /** * Get all non-abstract methods (including this method if it is not abstract) overriding this method. * @see AppInfo#findImplementations(CallString) * @see AppInfo#findImplementations(MethodRef) * @param checkAccess if false, find all non-abstract methods with same signature even if they do not * override this method. * @return a collection of all implementations of this method. */ public List<MethodInfo> getImplementations(final boolean checkAccess) { final List<MethodInfo> implementations = new LinkedList<MethodInfo>(); if (checkAccess && (isPrivate() || isStatic())) { if (isAbstract()) { throw new JavaClassFormatError("Method is private or static but abstract!: "+toString()); } implementations.add(this); return implementations; } if ("<init>".equals(getShortName())) { if (isAbstract()) { throw new JavaClassFormatError("Found abstract constructor, this isn't right..: "+toString()); } implementations.add(this); return implementations; } ClassVisitor visitor = new ClassVisitor() { public boolean visitClass(ClassInfo classInfo) { MethodInfo m = classInfo.getMethodInfo(getMethodSignature()); if ( m != null ) { if ( m.isPrivate() && !isPrivate() ) { // found an overriding method which is private .. this is interesting.. logger.error("Found private method "+m.getMethodSignature()+" in "+ classInfo.getClassName()+" overriding non-private method in "+ getClassInfo().getClassName()); } if ( !m.isAbstract() && (!checkAccess || m.overrides(MethodInfo.this,false)) ) { implementations.add(m); } } return true; } public void finishClass(ClassInfo classInfo) { } }; new ClassHierarchyTraverser(visitor).traverseDown(getClassInfo()); return implementations; } /** * Get a collection of classes local to this method. * @return a collection of local classes, or an empty collection of this method does not have local classes. */ public Collection<ClassInfo> getLocalClasses() { List<ClassInfo> classes = new LinkedList<ClassInfo>(); for (ClassInfo nested : getClassInfo().getDirectNestedClasses()) { if (nested.isLocalInnerClass() && this.equals(nested.getEnclosingMethodRef().getMethodInfo())) { classes.add(nested); } } return classes; } ////////////////////////////////////////////////////////////////////////////// // Internal stuff ////////////////////////////////////////////////////////////////////////////// /** * Should only be used by ClassInfo! * * @return the internal methodGen. */ protected MethodGen getInternalMethodGen() { return methodGen; } private void updateMethodCode() { if (!isAbstract() && !isNative()) { if (methodCode == null) { if (methodGen.getInstructionList() == null) { methodGen.setInstructionList(new InstructionList()); } methodCode = new MethodCode(this); } } else { if (methodCode != null) { methodGen.setInstructionList(null); methodGen.removeCodeAttributes(); methodGen.removeLineNumbers(); methodGen.removeExceptionHandlers(); methodGen.removeLocalVariables(); methodCode = null; } } } }