/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003-2005, University of Maryland * * 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 */ package edu.umd.cs.findbugs; import java.io.IOException; import org.apache.bcel.Constants; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.SignatureConverter; import edu.umd.cs.findbugs.ba.XFactory; import edu.umd.cs.findbugs.ba.XMethod; import edu.umd.cs.findbugs.bcel.OpcodeStackDetector; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.classfile.MethodDescriptor; import edu.umd.cs.findbugs.internalAnnotations.DottedClassName; import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName; import edu.umd.cs.findbugs.util.ClassName; import edu.umd.cs.findbugs.visitclass.DismantleBytecode; import edu.umd.cs.findbugs.visitclass.PreorderVisitor; import edu.umd.cs.findbugs.xml.XMLAttributeList; import edu.umd.cs.findbugs.xml.XMLOutput; /** * A BugAnnotation specifying a particular method in a particular class. A * MethodAnnotation may (optionally) have a SourceLineAnnotation directly * embedded inside it to indicate the range of source lines where the method is * defined. * * @author David Hovemeyer * @see BugAnnotation */ public class MethodAnnotation extends PackageMemberAnnotation { private static final long serialVersionUID = 1L; private static final boolean UGLY_METHODS = SystemProperties.getBoolean("ma.ugly"); public static final String DEFAULT_ROLE = "METHOD_DEFAULT"; private final String methodName; private final String methodSig; private String fullMethod; private final boolean isStatic; public static final String METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL = "METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL"; public static final String METHOD_DANGEROUS_TARGET = "METHOD_DANGEROUS_TARGET"; public static final String METHOD_RETURN_VALUE_OF = "METHOD_RETURN_VALUE_OF"; public static final String METHOD_SAFE_TARGET = "METHOD_SAFE_TARGET"; public static final String METHOD_EQUALS_USED = "METHOD_EQUALS_USED"; public static final String METHOD_CALLED = "METHOD_CALLED"; public static final String METHOD_SUPERCLASS_CONSTRUCTOR = "METHOD_SUPERCLASS_CONSTRUCTOR"; public static final String METHOD_CONSTRUCTOR = "METHOD_CONSTRUCTOR"; public static final String METHOD_OVERRIDDEN = "METHOD_OVERRIDDEN"; public static final String METHOD_DID_YOU_MEAN_TO_OVERRIDE = "METHOD_DID_YOU_MEAN_TO_OVERRIDE"; public static final String METHOD_COMPUTED_IN = "METHOD_COMPUTED_IN"; public static final String METHOD_ALTERNATIVE_TARGET = "METHOD_ALTERNATIVE_TARGET"; public static final String SHOULD_CALL = "SHOULD_CALL"; /** * Constructor. * * @param className * the name of the class containing the method * @param methodName * the name of the method * @param methodSig * the Java type signature of the method * @param isStatic * true if the method is static, false if not */ public MethodAnnotation(@DottedClassName String className, String methodName, String methodSig, boolean isStatic) { super(className, DEFAULT_ROLE); this.methodName = methodName; if (methodSig.indexOf(".") >= 0) { assert false : "signatures should not be dotted: " + methodSig; methodSig = methodSig.replace('.', '/'); } this.methodSig = methodSig; this.isStatic = isStatic; fullMethod = null; sourceLines = null; } /** * Factory method to create a MethodAnnotation from the method the given * visitor is currently visiting. * * @param visitor * the BetterVisitor currently visiting the method */ public static MethodAnnotation fromVisitedMethod(PreorderVisitor visitor) { String className = visitor.getDottedClassName(); MethodAnnotation result = new MethodAnnotation(className, visitor.getMethodName(), visitor.getMethodSig(), visitor .getMethod().isStatic()); // Try to find the source lines for the method SourceLineAnnotation srcLines = SourceLineAnnotation.fromVisitedMethod(visitor); result.setSourceLines(srcLines); return result; } /** * Factory method to create a MethodAnnotation from a method called by the * instruction the given visitor is currently visiting. * * @param visitor * the visitor * @return the MethodAnnotation representing the called method */ public static MethodAnnotation fromCalledMethod(DismantleBytecode visitor) { String className = visitor.getDottedClassConstantOperand(); String methodName = visitor.getNameConstantOperand(); String methodSig = visitor.getSigConstantOperand(); if (visitor instanceof OpcodeStackDetector && visitor.getOpcode() != Constants.INVOKESTATIC) { int params = PreorderVisitor.getNumberArguments(methodSig); OpcodeStackDetector oVisitor = (OpcodeStackDetector) visitor; if (!oVisitor.getStack().isTop() && oVisitor.getStack().getStackDepth() > params) { OpcodeStack.Item item = oVisitor.getStack().getStackItem(params); String cName = ClassName.fromFieldSignature(item.getSignature()); if (cName != null) className = cName; } } return fromCalledMethod(className, methodName, methodSig, visitor.getOpcode() == Constants.INVOKESTATIC); } /** * Factory method to create the MethodAnnotation from the classname, method * name, signature, etc. The method tries to look up source line information * for the method. * * @param className * name of the class containing the method * @param methodName * name of the method * @param methodSig * signature of the method * @param accessFlags * the access flags of the method * @return the MethodAnnotation */ public static MethodAnnotation fromForeignMethod(@SlashedClassName String className, String methodName, String methodSig, int accessFlags) { className = ClassName.toDottedClassName(className); // Create MethodAnnotation. // It won't have source lines yet. MethodAnnotation methodAnnotation = new MethodAnnotation(className, methodName, methodSig, (accessFlags & Constants.ACC_STATIC) != 0); SourceLineAnnotation sourceLines = SourceLineAnnotation.getSourceAnnotationForMethod(className, methodName, methodSig); methodAnnotation.setSourceLines(sourceLines); return methodAnnotation; } /** * Factory method to create the MethodAnnotation from the classname, method * name, signature, etc. The method tries to look up source line information * for the method. * * @param className * name of the class containing the method * @param methodName * name of the method * @param methodSig * signature of the method * @param isStatic * true if the method is static, false otherwise * @return the MethodAnnotation */ public static MethodAnnotation fromForeignMethod(String className, String methodName, String methodSig, boolean isStatic) { // FIXME: would be nice to do this without using BCEL className = ClassName.toDottedClassName(className); // Create MethodAnnotation. // It won't have source lines yet. MethodAnnotation methodAnnotation = new MethodAnnotation(className, methodName, methodSig, isStatic); if (AnalysisContext.currentAnalysisContext() != null) { SourceLineAnnotation sourceLines = SourceLineAnnotation .getSourceAnnotationForMethod(className, methodName, methodSig); methodAnnotation.setSourceLines(sourceLines); } return methodAnnotation; } /** * Create a MethodAnnotation from a method that is not directly accessible. * We will use the repository to try to find its class in order to populate * the information as fully as possible. * * @param className * class containing called method * @param methodName * name of called method * @param methodSig * signature of called method * @param isStatic * true if called method is static * @return the MethodAnnotation for the called method */ public static MethodAnnotation fromCalledMethod(String className, String methodName, String methodSig, boolean isStatic) { MethodAnnotation methodAnnotation = fromForeignMethod(className, methodName, methodSig, isStatic); methodAnnotation.setDescription("METHOD_CALLED"); return methodAnnotation; } /** * Create a MethodAnnotation from an XMethod. * * @param xmethod * the XMethod * @return the MethodAnnotation */ public static MethodAnnotation fromXMethod(XMethod xmethod) { return fromForeignMethod(xmethod.getClassName(), xmethod.getName(), xmethod.getSignature(), xmethod.isStatic()); } /** * Create a MethodAnnotation from a MethodDescriptor. * * @param methodDescriptor * the MethodDescriptor * @return the MethodAnnotation */ public static MethodAnnotation fromMethodDescriptor(MethodDescriptor methodDescriptor) { return fromForeignMethod(methodDescriptor.getSlashedClassName(), methodDescriptor.getName(), methodDescriptor.getSignature(), methodDescriptor.isStatic()); } /** * Get the method name. */ public String getMethodName() { return methodName; } public String getJavaSourceMethodName() { if (methodName.equals("<clinit>")) return "<static initializer for " + getSimpleClassName() + ">"; if (methodName.equals("<init>")) { return getSimpleClassName(); } return methodName; } /** * Get the method type signature. */ public String getMethodSignature() { return methodSig; } /** * Return whether or not the method is static. * * @return true if the method is static, false otherwise */ public boolean isStatic() { return isStatic; } /** * Convert to an XMethod. * * @return an XMethod specifying the same method as this MethodAnnotation */ public XMethod toXMethod() { return XFactory.createXMethod(className, methodName, methodSig, isStatic); } public MethodDescriptor toMethodDescriptor() { return DescriptorFactory.instance().getMethodDescriptor(this); } public void accept(BugAnnotationVisitor visitor) { visitor.visitMethodAnnotation(this); } @Override protected String formatPackageMember(String key, ClassAnnotation primaryClass) { if (key.equals("")) return UGLY_METHODS ? getUglyMethod() : getFullMethod(primaryClass); else if (key.equals("givenClass")) { if (methodName.equals("<init>")) { return "new " + shorten(primaryClass.getPackageName(), className) + getSignatureInClass(primaryClass); } if (className.equals(primaryClass.getClassName())) return getNameInClass(primaryClass); else return shorten(primaryClass.getPackageName(), className) + "." + getNameInClass(primaryClass); } else if (key.equals("name")) { return methodName; } else if (key.equals("nameAndSignature")) { return getNameInClass(primaryClass); } else if (key.equals("shortMethod")) return className + "." + methodName + "(...)"; else if (key.equals("hash")) { String tmp = getNameInClass(false, true, true); return className + "." + tmp; } else if (key.equals("returnType")) { int i = methodSig.indexOf(')'); String returnType = methodSig.substring(i + 1); String pkgName = primaryClass == null ? "" : primaryClass.getPackageName(); SignatureConverter converter = new SignatureConverter(returnType); return shorten(pkgName, converter.parseNext()); } else throw new IllegalArgumentException("unknown key " + key); } /** * Get the "full" method name. This is a format which looks sort of like a * method signature that would appear in Java source code. * * @param primaryClass * TODO */ public String getNameInClass(ClassAnnotation primaryClass) { return getNameInClass(true, false, false, false); } public String getSignatureInClass(ClassAnnotation primaryClass) { return getNameInClass(true, false, false, true); } public String getNameInClass(boolean shortenPackages, boolean useJVMMethodName, boolean hash) { return getNameInClass(shortenPackages, useJVMMethodName, hash, false); } /** * Get the "full" method name. This is a format which looks sort of like a * method signature that would appear in Java source code. * * note: If shortenPackeges==true, this will return the same value as * getNameInClass(), except that method caches the result and this one does * not. Calling this one may be slow. * * @param shortenPackages * whether to shorten package names if they are in java or in the * same package as this method. * @param useJVMMethodName * TODO * @param hash * TODO */ public String getNameInClass(boolean shortenPackages, boolean useJVMMethodName, boolean hash, boolean omitMethodName) { // Convert to "nice" representation StringBuilder result = new StringBuilder(); if (!omitMethodName) { if (useJVMMethodName) result.append(getMethodName()); else result.append(getJavaSourceMethodName()); } result.append('('); // append args SignatureConverter converter = new SignatureConverter(methodSig); if (converter.getFirst() != '(') throw new IllegalStateException("bad method signature " + methodSig); converter.skip(); boolean needsComma = false; while (converter.getFirst() != ')') { if (needsComma) if (hash) result.append(","); else result.append(", "); if (shortenPackages) result.append(removePackageName(converter.parseNext())); else result.append(converter.parseNext()); needsComma = true; } converter.skip(); result.append(')'); return result.toString(); } /** * Get the "full" method name. This is a format which looks sort of like a * method signature that would appear in Java source code. * * @param primaryClass * TODO */ public String getFullMethod(ClassAnnotation primaryClass) { if (fullMethod == null) { if (methodName.equals("<init>")) fullMethod = "new " + stripJavaLang(className) + getSignatureInClass(primaryClass); else fullMethod = stripJavaLang(className) + "." + getNameInClass(primaryClass); } return fullMethod; } public String stripJavaLang(@DottedClassName String className) { if (className.startsWith("java.lang.")) return className.substring(10); return className; } private String getUglyMethod() { return className + "." + methodName + " : " + methodSig.replace('/', '.'); } @Override public int hashCode() { return className.hashCode() + methodName.hashCode() + methodSig.hashCode(); } @Override public boolean equals(Object o) { if (!(o instanceof MethodAnnotation)) return false; MethodAnnotation other = (MethodAnnotation) o; return className.equals(other.className) && methodName.equals(other.methodName) && methodSig.equals(other.methodSig); } public int compareTo(BugAnnotation o) { if (!(o instanceof MethodAnnotation)) // BugAnnotations must be // Comparable with any type of // BugAnnotation return this.getClass().getName().compareTo(o.getClass().getName()); MethodAnnotation other = (MethodAnnotation) o; int cmp; cmp = className.compareTo(other.className); if (cmp != 0) return cmp; cmp = methodName.compareTo(other.methodName); if (cmp != 0) return cmp; return methodSig.compareTo(other.methodSig); } /* * ---------------------------------------------------------------------- * XML Conversion support * ---------------------------------------------------------------------- */ private static final String ELEMENT_NAME = "Method"; public void writeXML(XMLOutput xmlOutput) throws IOException { } public void writeXML(XMLOutput xmlOutput, boolean addMessages, boolean isPrimary) throws IOException { XMLAttributeList attributeList = new XMLAttributeList().addAttribute("classname", getClassName()) .addAttribute("name", getMethodName()).addAttribute("signature", getMethodSignature()) .addAttribute("isStatic", String.valueOf(isStatic())); if (isPrimary) attributeList.addAttribute("primary", "true"); String role = getDescription(); if (!role.equals(DEFAULT_ROLE)) attributeList.addAttribute("role", role); if (sourceLines == null && !addMessages) { xmlOutput.openCloseTag(ELEMENT_NAME, attributeList); } else { xmlOutput.openTag(ELEMENT_NAME, attributeList); if (sourceLines != null) { sourceLines.writeXML(xmlOutput); } if (addMessages) { xmlOutput.openTag(MESSAGE_TAG); xmlOutput.writeText(this.toString()); xmlOutput.closeTag(MESSAGE_TAG); } xmlOutput.closeTag(ELEMENT_NAME); } } @Override public boolean isSignificant() { String role = getDescription(); if (METHOD_DANGEROUS_TARGET.equals(role) || METHOD_DANGEROUS_TARGET_ACTUAL_GUARANTEED_NULL.equals(role) || METHOD_SAFE_TARGET.equals(role) || METHOD_EQUALS_USED.equals(role) || METHOD_COMPUTED_IN.equals(role)) return false; return true; } } // vim:ts=4