[Fb-contrib-commit] SF.net SVN: fb-contrib: [654] trunk/fb-contrib/src/com/mebigfatguy/ fbcontrib/
Brought to you by:
dbrosius
From: <dbr...@us...> - 2006-09-24 19:49:53
|
Revision: 654 http://svn.sourceforge.net/fb-contrib/?rev=654&view=rev Author: dbrosius Date: 2006-09-24 12:49:47 -0700 (Sun, 24 Sep 2006) Log Message: ----------- rename for those with weak stomachs Added Paths: ----------- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/PossiblyRedundantMethodCalls.java Added: trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/PossiblyRedundantMethodCalls.java =================================================================== --- trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/PossiblyRedundantMethodCalls.java (rev 0) +++ trunk/fb-contrib/src/com/mebigfatguy/fbcontrib/detect/PossiblyRedundantMethodCalls.java 2006-09-24 19:49:47 UTC (rev 654) @@ -0,0 +1,265 @@ +/* + * fb-contrib - Auxilliary detectors for Java programs + * Copyright (C) 2005-2006 Dave Brosius + * + * 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 com.mebigfatguy.fbcontrib.detect; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.bcel.classfile.Code; +import org.apache.bcel.classfile.CodeException; +import org.apache.bcel.generic.Type; + +import com.mebigfatguy.fbcontrib.collect.Statistics; +import com.mebigfatguy.fbcontrib.utils.Integer14; +import com.mebigfatguy.fbcontrib.utils.RegisterUtils; + +import edu.umd.cs.findbugs.BugInstance; +import edu.umd.cs.findbugs.BugReporter; +import edu.umd.cs.findbugs.BytecodeScanningDetector; +import edu.umd.cs.findbugs.FieldAnnotation; +import edu.umd.cs.findbugs.OpcodeStack; +import edu.umd.cs.findbugs.ba.ClassContext; + +/** + * looks for calls of the same method on the same object when that object hasn't changed. + * This often is redundant, and the second call can be removed, or combined. + */ +public class PossiblyRedundantMethodCalls extends BytecodeScanningDetector +{ + public static final String PRMC_RISKY_FIELD_USER_KEY = "fbcontrib.PRMC.riskynames"; + public static final String PRMC_RISKY_CLASS_USER_KEY = "fbcontrib.PRMC.riskyclasses"; + + private static Set<String> riskyMethodNameContents = new HashSet<String>(); + static { + riskyMethodNameContents.add("next"); + riskyMethodNameContents.add("add"); + riskyMethodNameContents.add("append"); + riskyMethodNameContents.add("put"); + riskyMethodNameContents.add("remove"); + riskyMethodNameContents.add("read"); + riskyMethodNameContents.add("write"); + riskyMethodNameContents.add("push"); + riskyMethodNameContents.add("pop"); + riskyMethodNameContents.add("skip"); + riskyMethodNameContents.add("clone"); + + String userNameProp = System.getProperty(PRMC_RISKY_FIELD_USER_KEY); + if (userNameProp != null) { + String[] userNames = userNameProp.split("\\s*,\\s*"); + for (String name : userNames) + riskyMethodNameContents.add(name.toLowerCase()); + } + } + private static Set<String> riskyClassNames = new HashSet<String>(); + static { + riskyClassNames.add("java/nio/ByteBuffer"); + riskyClassNames.add("java/io/DataInputStream"); + riskyClassNames.add("java/io/ObjectInputStream"); + String userNameProp = System.getProperty(PRMC_RISKY_CLASS_USER_KEY); + if (userNameProp != null) { + String[] userNames = userNameProp.split("\\s*,\\s*"); + for (String name : userNames) + riskyClassNames.add(name); + } + } + + private BugReporter bugReporter; + private OpcodeStack stack = null; + private Map<Integer, MethodCall> localMethodCalls = null; + private Map<String, MethodCall> fieldMethodCalls = null; + private Set<Integer> branchTargets = null; + + /** + * constructs a PRMC detector given the reporter to report bugs on + * @param bugReporter the sync of bug reports + */ + public PossiblyRedundantMethodCalls(BugReporter bugReporter) { + this.bugReporter = bugReporter; + } + + /** + * implements the visitor to create and clear the stack, method call maps, and branch targets + * + * @param classContext the context object of the currently visited class + */ + public void visitClassContext(ClassContext classContext) { + try { + stack = new OpcodeStack(); + localMethodCalls = new HashMap<Integer, MethodCall>(); + fieldMethodCalls = new HashMap<String, MethodCall>(); + branchTargets = new HashSet<Integer>(); + super.visitClassContext(classContext); + } finally { + stack = null; + localMethodCalls = null; + fieldMethodCalls = null; + branchTargets = null; + } + } + + /** + * implements the visitor to reset the stack, and method call maps for new method + * + * @param obj the context object of the currently parsed code block + */ + public void visitCode(Code obj) { + stack.resetForMethodEntry(this); + localMethodCalls.clear(); + fieldMethodCalls.clear(); + branchTargets.clear(); + CodeException[] codeExceptions = obj.getExceptionTable(); + for (CodeException codeEx : codeExceptions) { + branchTargets.add(codeEx.getHandlerPC()); + } + super.visitCode(obj); + } + + /** + * implements the visitor to look for repetitive calls to the same method on the same object + * using the same constant parameters. These methods must return a value. + * + * @param seen the opcode of the currently parsed instruction + */ + public void sawOpcode(int seen) { + try { + stack.mergeJumps(this); + if (branchTargets.remove(Integer14.valueOf(getPC()))) { + localMethodCalls.clear(); + fieldMethodCalls.clear(); + } + + if (((seen >= IFEQ) && (seen <= GOTO)) || ((seen >= IFNULL) && (seen <= GOTO_W))) { + branchTargets.add(Integer14.valueOf(getBranchTarget())); + } else if ((seen == TABLESWITCH) || (seen == LOOKUPSWITCH)) { + int[] offsets = getSwitchOffsets(); + int pc = getPC(); + for (int offset : offsets) { + branchTargets.add(Integer14.valueOf(offset + pc)); + } + } else if ((seen == ASTORE) || ((seen >= ASTORE_0) && (seen <= ASTORE_3))) { + localMethodCalls.remove(Integer14.valueOf(RegisterUtils.getAStoreReg(this, seen))); + } else if (seen == PUTFIELD) { + fieldMethodCalls.remove(getNameConstantOperand()); + } else if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE)) { + String className = getClassConstantOperand(); + String methodName = getNameConstantOperand(); + String signature = getSigConstantOperand(); + int parmCount = Type.getArgumentTypes(signature).length; + if (stack.getStackDepth() > parmCount) { + Object[] parmConstants = new Object[parmCount]; + for (int i = 0; i < parmCount; i++) { + OpcodeStack.Item parm = stack.getStackItem(i); + parmConstants[i] = parm.getConstant(); + if (parmConstants[i] == null) + return; + } + OpcodeStack.Item obj = stack.getStackItem(parmCount); + int reg = obj.getRegisterNumber(); + FieldAnnotation fa = obj.getField(); + + MethodCall mc; + if (reg >= 0) { + mc = localMethodCalls.get(Integer14.valueOf(reg)); + } else if (fa != null) { + mc = fieldMethodCalls.get(fa.getFieldName()); + } else + return; + + if (mc != null) { + if (!signature.endsWith("V") && methodName.equals(mc.getName()) && signature.equals(mc.getSignature()) && !isRiskyName(className, methodName)) { + Object[] parms = mc.getParms(); + if (Arrays.equals(parms, parmConstants)) { + Statistics statistics = Statistics.getStatistics(); + bugReporter.reportBug(new BugInstance(this, "PRMC_REDUNDANT_METHOD_CALLS", statistics.isSimpleGetter(getClassConstantOperand(), methodName, signature) ? EXP_PRIORITY : NORMAL_PRIORITY) + .addClass(this) + .addMethod(this) + .addSourceLine(this) + .addString(methodName + signature)); + } + } + if (reg >= 0) { + localMethodCalls.remove(Integer14.valueOf(reg)); + } else { + fieldMethodCalls.remove(fa.getFieldName()); + } + } else { + if (reg >= 0) { + localMethodCalls.put(Integer14.valueOf(reg), new MethodCall(methodName, signature, parmConstants)); + } else if (fa != null) { + fieldMethodCalls.put(fa.getFieldName(), new MethodCall(methodName, signature, parmConstants)); + } + } + } + } + } finally { + stack.sawOpcode(this, seen); + } + } + + /** + * returns true if the class or method name contains a pattern that is considered likely to be this modifying + * + * @param className the class name to check + * @param methodName the method name to check + * @return whether the method sounds like it modifies this + */ + private boolean isRiskyName(String className, String methodName) { + if (riskyClassNames.contains(className)) + return true; + + methodName = methodName.toLowerCase(Locale.ENGLISH); + for (String riskyName : riskyMethodNameContents) { + if (methodName.indexOf(riskyName) >= 0) + return true; + } + return false; + } + + /** + * contains information about a method call + */ + static class MethodCall + { + private String methodName; + private String methodSignature; + private Object[] methodParms; + + public MethodCall(String name, String signature, Object[] parms) { + methodName = name; + methodSignature = signature; + methodParms = parms; + } + + public String getName() { + return methodName; + } + + public String getSignature() { + return methodSignature; + } + + public Object[] getParms() { + return methodParms; + } + } +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |