/* * FindBugs - Find bugs in Java programs * Copyright (C) 2005 Dave Brosius <dbrosius@users.sourceforge.net> * Copyright (C) 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.detect; import java.util.HashSet; import java.util.Set; import org.apache.bcel.Constants; import org.apache.bcel.Repository; import org.apache.bcel.classfile.Attribute; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.classfile.Synthetic; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.BugInstance; import edu.umd.cs.findbugs.BugReporter; import edu.umd.cs.findbugs.BytecodeScanningDetector; import edu.umd.cs.findbugs.StatelessDetector; import edu.umd.cs.findbugs.ba.ClassContext; public class UselessSubclassMethod extends BytecodeScanningDetector implements StatelessDetector { enum State { SEEN_NOTHING, SEEN_PARM, SEEN_LAST_PARM, SEEN_INVOKE, SEEN_RETURN, SEEN_INVALID }; private BugReporter bugReporter; private String superclassName; private State state; private int curParm; private int curParmOffset; private int invokePC; private Type[] argTypes; private Set<String> interfaceMethods = null; public UselessSubclassMethod(BugReporter bugReporter) { this.bugReporter = bugReporter; } @Override public void visitClassContext(ClassContext classContext) { try { JavaClass cls = classContext.getJavaClass(); superclassName = cls.getSuperclassName(); JavaClass[] interfaces = null; if (cls.isClass() && ((cls.getAccessFlags() & Constants.ACC_ABSTRACT) != 0)) { interfaces = cls.getAllInterfaces(); interfaceMethods = new HashSet<String>(); for (JavaClass aInterface : interfaces) { Method[] infMethods = aInterface.getMethods(); for (Method meth : infMethods) { interfaceMethods.add(meth.getName() + meth.getSignature()); } } } } catch (ClassNotFoundException cnfe) { bugReporter.reportMissingClass(cnfe); } super.visitClassContext(classContext); } @Override public void visitAfter(JavaClass obj) { interfaceMethods = null; super.visitAfter(obj); } @Override public void visitMethod(Method obj) { if ((interfaceMethods != null) && ((obj.getAccessFlags() & Constants.ACC_ABSTRACT) != 0)) { String curDetail = obj.getName() + obj.getSignature(); for (String infMethodDetail : interfaceMethods) { if (curDetail.equals(infMethodDetail)) bugReporter.reportBug(new BugInstance(this, "USM_USELESS_ABSTRACT_METHOD", LOW_PRIORITY).addClassAndMethod( getClassContext().getJavaClass(), obj)); } } super.visitMethod(obj); } @Override public void visitCode(Code obj) { try { String methodName = getMethodName(); if (!methodName.equals("<init>") && !methodName.equals("clone") && ((getMethod().getAccessFlags() & (Constants.ACC_STATIC | Constants.ACC_SYNTHETIC)) == 0)) { /* * for some reason, access flags doesn't return Synthetic, so do * this hocus pocus */ Attribute[] atts = getMethod().getAttributes(); for (Attribute att : atts) { if (att.getClass().equals(Synthetic.class)) return; } byte[] codeBytes = obj.getCode(); if ((codeBytes.length == 0) || (codeBytes[0] != ALOAD_0)) return; state = State.SEEN_NOTHING; invokePC = 0; super.visitCode(obj); if ((state == State.SEEN_RETURN) && (invokePC != 0)) { // Do this check late, as it is potentially expensive Method superMethod = findSuperclassMethod(superclassName, getMethod()); if ((superMethod == null) || accessModifiersAreDifferent(getMethod(), superMethod)) return; bugReporter.reportBug(new BugInstance(this, "USM_USELESS_SUBCLASS_METHOD", LOW_PRIORITY).addClassAndMethod( this).addSourceLine(this, invokePC)); } } } catch (ClassNotFoundException cnfe) { bugReporter.reportMissingClass(cnfe); } } @Override public void sawOpcode(int seen) { switch (state) { case SEEN_NOTHING: if (seen == ALOAD_0) { argTypes = Type.getArgumentTypes(this.getMethodSig()); curParm = 0; curParmOffset = 1; if (argTypes.length > 0) state = State.SEEN_PARM; else state = State.SEEN_LAST_PARM; } else state = State.SEEN_INVALID; break; case SEEN_PARM: if (curParm >= argTypes.length) state = State.SEEN_INVALID; else { String signature = argTypes[curParm++].getSignature(); char typeChar0 = signature.charAt(0); if ((typeChar0 == 'L') || (typeChar0 == '[')) { checkParm(seen, ALOAD_0, ALOAD, 1); } else if (typeChar0 == 'D') { checkParm(seen, DLOAD_0, DLOAD, 2); } else if (typeChar0 == 'F') { checkParm(seen, FLOAD_0, FLOAD, 1); } else if (typeChar0 == 'I' || typeChar0 == 'Z' || typeChar0 == 'S' || typeChar0 == 'C') { checkParm(seen, ILOAD_0, ILOAD, 1); } else if (typeChar0 == 'J') { checkParm(seen, LLOAD_0, LLOAD, 2); } else state = State.SEEN_INVALID; if ((state != State.SEEN_INVALID) && (curParm >= argTypes.length)) state = State.SEEN_LAST_PARM; } break; case SEEN_LAST_PARM: if ((seen == INVOKENONVIRTUAL) && getMethodName().equals(getNameConstantOperand()) && getMethodSig().equals(getSigConstantOperand())) { invokePC = getPC(); state = State.SEEN_INVOKE; } else state = State.SEEN_INVALID; break; case SEEN_INVOKE: Type returnType = getMethod().getReturnType(); char retSigChar0 = returnType.getSignature().charAt(0); if ((retSigChar0 == 'V') && (seen == RETURN)) state = State.SEEN_RETURN; else if (((retSigChar0 == 'L') || (retSigChar0 == '[')) && (seen == ARETURN)) state = State.SEEN_RETURN; else if ((retSigChar0 == 'D') && (seen == DRETURN)) state = State.SEEN_RETURN; else if ((retSigChar0 == 'F') && (seen == FRETURN)) state = State.SEEN_RETURN; else if ((retSigChar0 == 'I' || retSigChar0 == 'S' || retSigChar0 == 'C' || retSigChar0 == 'B' || retSigChar0 == 'Z') && (seen == IRETURN)) state = State.SEEN_RETURN; else if ((retSigChar0 == 'J') && (seen == LRETURN)) state = State.SEEN_RETURN; else state = State.SEEN_INVALID; break; case SEEN_RETURN: state = State.SEEN_INVALID; break; } } private void checkParm(int seen, int fastOpBase, int slowOp, int parmSize) { if ((curParmOffset >= 1) && (curParmOffset <= 3)) { if (seen == (fastOpBase + curParmOffset)) curParmOffset += parmSize; else state = State.SEEN_INVALID; } else if (curParmOffset == 0) state = State.SEEN_INVALID; else if ((seen == slowOp) && (getRegisterOperand() == curParmOffset)) curParmOffset += parmSize; else state = State.SEEN_INVALID; } private Method findSuperclassMethod(String superclassName, Method subclassMethod) throws ClassNotFoundException { String methodName = subclassMethod.getName(); Type[] subArgs = null; JavaClass superClass = Repository.lookupClass(superclassName); Method[] methods = superClass.getMethods(); outer: for (Method m : methods) { if (m.getName().equals(methodName)) { if (subArgs == null) subArgs = Type.getArgumentTypes(subclassMethod.getSignature()); Type[] superArgs = Type.getArgumentTypes(m.getSignature()); if (subArgs.length == superArgs.length) { for (int j = 0; j < subArgs.length; j++) { if (!superArgs[j].equals(subArgs[j])) continue outer; } return m; } } } if (!superclassName.equals("Object")) { String superSuperClassName = superClass.getSuperclassName(); if (superSuperClassName.equals(superclassName)) { throw new ClassNotFoundException("superclass of " + superclassName + " is itself"); } return findSuperclassMethod(superClass.getSuperclassName(), subclassMethod); } return null; } private boolean accessModifiersAreDifferent(Method m1, Method m2) { int access1 = m1.getAccessFlags() & (Constants.ACC_PRIVATE | Constants.ACC_PROTECTED | Constants.ACC_PUBLIC | Constants.ACC_FINAL); int access2 = m2.getAccessFlags() & (Constants.ACC_PRIVATE | Constants.ACC_PROTECTED | Constants.ACC_PUBLIC | Constants.ACC_FINAL); return access1 != access2; } }