/* * fb-contrib - Auxiliary detectors for Java programs * Copyright (C) 2005-2017 Kevin Lubick * Copyright (C) 2005-2017 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 static com.mebigfatguy.fbcontrib.utils.OpcodeUtils.isALoad; import static com.mebigfatguy.fbcontrib.utils.OpcodeUtils.isAStore; import static com.mebigfatguy.fbcontrib.utils.OpcodeUtils.isStandardInvoke; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.Method; import com.mebigfatguy.fbcontrib.utils.RegisterUtils; import com.mebigfatguy.fbcontrib.utils.SignatureUtils; import com.mebigfatguy.fbcontrib.utils.TernaryPatcher; import com.mebigfatguy.fbcontrib.utils.ToString; import com.mebigfatguy.fbcontrib.utils.Values; import edu.umd.cs.findbugs.BytecodeScanningDetector; import edu.umd.cs.findbugs.OpcodeStack; import edu.umd.cs.findbugs.SourceLineAnnotation; import edu.umd.cs.findbugs.ba.ClassContext; abstract class LocalTypeDetector extends BytecodeScanningDetector { private OpcodeStack stack; private Map<Integer, RegisterInfo> suspectLocals; private int classVersion; /** * Should return a map of constructors that should be watched, as well as version number of Java that the given constructor becomes a bad idea. * * e.g. StringBuffer was the only way to efficiently concatenate a string until the faster, non-thread safe StringBuilder was introduced in 1.5. Thus, in * code that targets before 1.5, FindBugs should not report a LocalSynchronizedCollection bug. Therefore, the entry <"java/lang/StringBuffer", * Constants.MAJOR_1_5> is in the returned map. * * @return the map of watched constructors */ protected abstract Map<String, Integer> getWatchedConstructors(); /** * Should return a map of a class and a set of "factory" methods that create types that should be reported buggy (when made as local variables). * * @return map of factory methods */ protected abstract Map<String, Set<String>> getWatchedClassMethods(); /** * returns a set of self returning methods, that is, methods that when called on a a synchronized collection return themselves. * * @return a set of self referential methods */ protected abstract Set<String> getSelfReturningMethods(); /** * Given this RegisterInfo, report an appropriate bug. * * @param cri * the register info */ protected abstract void reportBug(RegisterInfo cri); /** * implements the visitor to create and clear the stack and suspectLocals * * @param classContext * the context object of the currently parsed class */ @Override public void visitClassContext(ClassContext classContext) { try { stack = new OpcodeStack(); suspectLocals = new HashMap<>(); classVersion = classContext.getJavaClass().getMajor(); super.visitClassContext(classContext); } finally { stack = null; suspectLocals = null; } } /** * implements the visitor to collect parameter registers * * @param obj * the context object of the currently parsed method */ @Override public void visitMethod(Method obj) { suspectLocals.clear(); int[] parmRegs = RegisterUtils.getParameterRegisters(obj); for (int pr : parmRegs) { suspectLocals.put(Integer.valueOf(pr), new RegisterInfo(RegisterUtils.getLocalVariableEndRange(obj.getLocalVariableTable(), pr, 0))); } } /** * implements the visitor to reset the stack * * @param obj * the context object of the currently parsed code block */ @Override public void visitCode(Code obj) { stack.resetForMethodEntry(this); super.visitCode(obj); for (Map.Entry<Integer, RegisterInfo> entry : suspectLocals.entrySet()) { RegisterInfo cri = entry.getValue(); if (!cri.getIgnore()) { reportBug(cri); } } } /** * implements the visitor to find the constructors defined in getWatchedConstructors() and the method calls in getWatchedClassMethods() * * @param seen * the opcode of the currently parsed instruction */ @Override public void sawOpcode(int seen) { Integer tosIsSyncColReg = null; try { stack.precomputation(this); if (seen == INVOKESPECIAL) { tosIsSyncColReg = checkConstructors(); } else if (seen == INVOKESTATIC) { tosIsSyncColReg = checkStaticCreations(); } else if ((seen == INVOKEVIRTUAL) || (seen == INVOKEINTERFACE)) { tosIsSyncColReg = checkSelfReturningMethods(); } else if (isAStore(seen)) { dealWithStoring(seen); } else if (isALoad(seen)) { int reg = RegisterUtils.getALoadReg(this, seen); RegisterInfo cri = suspectLocals.get(Integer.valueOf(reg)); if ((cri != null) && !cri.getIgnore()) { tosIsSyncColReg = Integer.valueOf(reg); } } else if (((seen == PUTFIELD) || (seen == ARETURN)) && (stack.getStackDepth() > 0)) { OpcodeStack.Item item = stack.getStackItem(0); suspectLocals.remove(item.getUserValue()); } if (!suspectLocals.isEmpty()) { if (isStandardInvoke(seen)) { String sig = getSigConstantOperand(); int argCount = SignatureUtils.getNumParameters(sig); if (stack.getStackDepth() >= argCount) { for (int i = 0; i < argCount; i++) { OpcodeStack.Item item = stack.getStackItem(i); RegisterInfo cri = suspectLocals.get(item.getUserValue()); if (cri != null) { if (SignatureUtils.similarPackages(SignatureUtils.getPackageName(SignatureUtils.stripSignature(getClassConstantOperand())), SignatureUtils.getPackageName(SignatureUtils.stripSignature(this.getClassName())), 2)) { cri.setPriority(LOW_PRIORITY); } else { cri.setIgnore(); } } } } } else if (seen == MONITORENTER) { // Assume if synchronized blocks are used then something // tricky is going on. // There is really no valid reason for this, other than // folks who use // synchronized blocks tend to know what's going on. if (stack.getStackDepth() > 0) { OpcodeStack.Item item = stack.getStackItem(0); suspectLocals.remove(item.getUserValue()); } } else if ((seen == AASTORE) && (stack.getStackDepth() > 0)) { OpcodeStack.Item item = stack.getStackItem(0); suspectLocals.remove(item.getUserValue()); } } reportTroublesomeLocals(); } finally { TernaryPatcher.pre(stack, seen); stack.sawOpcode(this, seen); TernaryPatcher.post(stack, seen); if ((tosIsSyncColReg != null) && (stack.getStackDepth() > 0)) { OpcodeStack.Item item = stack.getStackItem(0); item.setUserValue(tosIsSyncColReg); } } } protected void dealWithStoring(int seen) { if (stack.getStackDepth() > 0) { OpcodeStack.Item item = stack.getStackItem(0); int reg = RegisterUtils.getAStoreReg(this, seen); if (item.getUserValue() != null) { if (!suspectLocals.containsKey(Integer.valueOf(reg))) { RegisterInfo cri = new RegisterInfo(SourceLineAnnotation.fromVisitedInstruction(this), RegisterUtils.getLocalVariableEndRange(getMethod().getLocalVariableTable(), reg, getNextPC())); suspectLocals.put(Integer.valueOf(reg), cri); } } else { RegisterInfo cri = suspectLocals.get(Integer.valueOf(reg)); if (cri == null) { cri = new RegisterInfo(RegisterUtils.getLocalVariableEndRange(getMethod().getLocalVariableTable(), reg, getNextPC())); suspectLocals.put(Integer.valueOf(reg), cri); } cri.setIgnore(); } } } protected Integer checkStaticCreations() { Integer tosIsSyncColReg = null; Map<String, Set<String>> mapOfClassToMethods = getWatchedClassMethods(); for (Entry<String, Set<String>> entry : mapOfClassToMethods.entrySet()) { if (entry.getKey().equals(getClassConstantOperand()) && entry.getValue().contains(getNameConstantOperand())) { tosIsSyncColReg = Values.NEGATIVE_ONE; break; } } return tosIsSyncColReg; } protected Integer checkSelfReturningMethods() { Integer tosIsSyncColReg = null; Set<String> selfReturningMethods = getSelfReturningMethods(); String methodName = getClassConstantOperand() + '.' + getNameConstantOperand(); for (String selfRefNames : selfReturningMethods) { if (methodName.equals(selfRefNames)) { int numParameters = SignatureUtils.getNumParameters(getSigConstantOperand()); if (stack.getStackDepth() > numParameters) { OpcodeStack.Item item = stack.getStackItem(numParameters); tosIsSyncColReg = (Integer) item.getUserValue(); } break; } } return tosIsSyncColReg; } protected Integer checkConstructors() { Integer tosIsSyncColReg = null; if (Values.CONSTRUCTOR.equals(getNameConstantOperand())) { Integer minVersion = getWatchedConstructors().get(getClassConstantOperand()); if ((minVersion != null) && (classVersion >= minVersion.intValue())) { tosIsSyncColReg = Values.NEGATIVE_ONE; } } return tosIsSyncColReg; } protected void reportTroublesomeLocals() { int curPC = getPC(); Iterator<RegisterInfo> it = suspectLocals.values().iterator(); while (it.hasNext()) { RegisterInfo cri = it.next(); if (cri.getEndPCRange() < curPC) { if (!cri.getIgnore()) { reportBug(cri); } it.remove(); } } } protected static class RegisterInfo { private SourceLineAnnotation slAnnotation; private int priority = HIGH_PRIORITY; private int endPCRange = Integer.MAX_VALUE; public RegisterInfo(SourceLineAnnotation sla, int endPC) { slAnnotation = sla; endPCRange = endPC; } public RegisterInfo(int endPC) { slAnnotation = null; endPCRange = endPC; } public SourceLineAnnotation getSourceLineAnnotation() { return slAnnotation; } public void setEndPCRange(int pc) { endPCRange = pc; } public int getEndPCRange() { return endPCRange; } public void setIgnore() { slAnnotation = null; } public boolean getIgnore() { return slAnnotation == null; } public void setPriority(int newPriority) { if (newPriority > priority) { priority = newPriority; } } public int getPriority() { return priority; } @Override public String toString() { return ToString.build(this); } } }