📄 findinconsistentsync2.java
字号:
/* * 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.detect;import edu.umd.cs.findbugs.*;import edu.umd.cs.findbugs.ba.*;import edu.umd.cs.findbugs.ba.type.*;import edu.umd.cs.findbugs.ba.vna.*;import edu.umd.cs.findbugs.props.WarningPropertySet;import java.util.*;import org.apache.bcel.Constants;import org.apache.bcel.classfile.*;import org.apache.bcel.generic.*;/** * Find instance fields which are sometimes accessed (read or written) * with the receiver lock held and sometimes without. * These are candidates to be data races. * * @author David Hovemeyer * @author Bill Pugh */public class FindInconsistentSync2 implements Detector { private static final boolean DEBUG = SystemProperties.getBoolean("fis.debug"); private static final boolean SYNC_ACCESS = true; // Boolean.getBoolean("fis.syncAccess"); private static final boolean ADJUST_SUBCLASS_ACCESSES = !SystemProperties.getBoolean("fis.noAdjustSubclass"); private static final boolean EVAL = SystemProperties.getBoolean("fis.eval"); /* ---------------------------------------------------------------------- * Tuning parameters * ---------------------------------------------------------------------- */ /** * Minimum percent of unbiased field accesses that must be synchronized in * order to report a field as inconsistently synchronized. * This is meant to distinguish incidental synchronization from * intentional synchronization. */ private static final int MIN_SYNC_PERCENT = SystemProperties.getInteger("findbugs.fis.minSyncPercent", 50).intValue(); /** * Bias that writes are given with respect to reads. * The idea is that this should be above 1.0, because unsynchronized * writes are more dangerous than unsynchronized reads. */ private static final double WRITE_BIAS = Double.parseDouble(SystemProperties.getProperty("findbugs.fis.writeBias", "2.0")); /** * Factor which the biased number of unsynchronized accesses is multiplied by. * I.e., for factor <i>f</i>, if <i>nUnsync</i> is the biased number of unsynchronized * accesses, and <i>nSync</i> is the biased number of synchronized accesses, * and * <pre> * <i>f</i>(<i>nUnsync</i>) > <i>nSync</i> * </pre> * then we report a bug. Default value is 2.0, which means that we * report a bug if more than 1/3 of accesses are unsynchronized. * <p/> * <p> Note that <code>MIN_SYNC_PERCENT</code> also influences * whether we report a bug: it specifies the minimum unbiased percentage * of synchronized accesses. */ private static final double UNSYNC_FACTOR = Double.parseDouble(SystemProperties.getProperty("findbugs.fis.unsyncFactor", "2.0")); /* ---------------------------------------------------------------------- * Helper classes * ---------------------------------------------------------------------- */ private static final int UNLOCKED = 0; private static final int LOCKED = 1; private static final int READ = 0; private static final int WRITE = 2; private static final int READ_UNLOCKED = READ | UNLOCKED; private static final int WRITE_UNLOCKED = WRITE | UNLOCKED; private static final int READ_LOCKED = READ | LOCKED; private static final int WRITE_LOCKED = WRITE | LOCKED; /** * The access statistics for a field. * Stores the number of locked and unlocked reads and writes, * as well as the number of accesses made with a lock held. */ private static class FieldStats { private int[] countList = new int[4]; private int numLocalLocks = 0; private int numGetterMethodAccesses = 0; private LinkedList<SourceLineAnnotation> unsyncAccessList = new LinkedList<SourceLineAnnotation>(); private LinkedList<SourceLineAnnotation> syncAccessList = new LinkedList<SourceLineAnnotation>(); public void addAccess(int kind) { countList[kind]++; } public int getNumAccesses(int kind) { return countList[kind]; } public void addLocalLock() { numLocalLocks++; } public int getNumLocalLocks() { return numLocalLocks; } public void addGetterMethodAccess() { numGetterMethodAccesses++; } public int getNumGetterMethodAccesses() { return numGetterMethodAccesses; } public void addAccess(ClassContext classContext, Method method, InstructionHandle handle, boolean isLocked) { if (!SYNC_ACCESS && isLocked) return; SourceLineAnnotation accessSourceLine = SourceLineAnnotation.fromVisitedInstruction(classContext, method, handle.getPosition()); if (accessSourceLine != null) (isLocked ? syncAccessList : unsyncAccessList).add(accessSourceLine); } public Iterator<SourceLineAnnotation> unsyncAccessIterator() { return unsyncAccessList.iterator(); } public Iterator<SourceLineAnnotation> syncAccessIterator() { return syncAccessList.iterator(); } } /* ---------------------------------------------------------------------- * Fields * ---------------------------------------------------------------------- */ private BugReporter bugReporter; private Map<XField, FieldStats> statMap = new HashMap<XField, FieldStats>(); /* ---------------------------------------------------------------------- * Public methods * ---------------------------------------------------------------------- */ public FindInconsistentSync2(BugReporter bugReporter) { this.bugReporter = bugReporter; } public void visitClassContext(ClassContext classContext) { JavaClass javaClass = classContext.getJavaClass(); if (DEBUG) System.out.println("******** Analyzing class " + javaClass.getClassName()); // Build self-call graph SelfCalls selfCalls = new SelfCalls(classContext) { @Override public boolean wantCallsFor(Method method) { return !method.isPublic(); } }; Set<Method> lockedMethodSet; Set<Method> publicReachableMethods; try { selfCalls.execute(); CallGraph callGraph = selfCalls.getCallGraph(); if (DEBUG) System.out.println("Call graph (not unlocked methods): " + callGraph.getNumVertices() + " nodes, " + callGraph.getNumEdges() + " edges"); // Find call edges that are obviously locked Set<CallSite> obviouslyLockedSites = findObviouslyLockedCallSites(classContext, selfCalls); lockedMethodSet = findNotUnlockedMethods(classContext, selfCalls, obviouslyLockedSites); lockedMethodSet.retainAll(findLockedMethods(classContext, selfCalls, obviouslyLockedSites)); publicReachableMethods = findPublicReachableMethods(classContext, selfCalls); } catch (CFGBuilderException e) { bugReporter.logError("Error finding locked call sites", e); return; } catch (DataflowAnalysisException e) { bugReporter.logError("Error finding locked call sites", e); return; } for (Method method : publicReachableMethods) { if (classContext.getMethodGen(method) == null) continue; /* if (isConstructor(method.getName())) continue; */ if (method.getName().startsWith("access$")) // Ignore inner class access methods; // we will treat calls to them as field accesses continue; try { analyzeMethod(classContext, method, lockedMethodSet); } catch (CFGBuilderException e) { bugReporter.logError("Error analyzing method", e); } catch (DataflowAnalysisException e) { bugReporter.logError("Error analyzing method", e); } } } public void report() { for (XField xfield : statMap.keySet()) { FieldStats stats = statMap.get(xfield); JCIPAnnotationDatabase jcipAnotationDatabase = AnalysisContext.currentAnalysisContext() .getJCIPAnnotationDatabase(); boolean guardedByThis = "this".equals(jcipAnotationDatabase.getFieldAnnotation(xfield, "GuardedBy")); boolean notThreadSafe = jcipAnotationDatabase.hasClassAnnotation(xfield.getClassName(), "NotThreadSafe"); boolean threadSafe = jcipAnotationDatabase.hasClassAnnotation(xfield.getClassName().replace('/','.'), "ThreadSafe"); if (notThreadSafe) continue; WarningPropertySet propertySet = new WarningPropertySet(); int numReadUnlocked = stats.getNumAccesses(READ_UNLOCKED); int numWriteUnlocked = stats.getNumAccesses(WRITE_UNLOCKED); int numReadLocked = stats.getNumAccesses(READ_LOCKED); int numWriteLocked = stats.getNumAccesses(WRITE_LOCKED); int locked = numReadLocked + numWriteLocked; int biasedLocked = numReadLocked + (int) (WRITE_BIAS * numWriteLocked); int unlocked = numReadUnlocked + numWriteUnlocked; int biasedUnlocked = numReadUnlocked + (int) (WRITE_BIAS * numWriteUnlocked); int writes = numWriteLocked + numWriteUnlocked; if (unlocked == 0) { continue;// propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_UNLOCKED); } if (guardedByThis) { propertySet.addProperty(InconsistentSyncWarningProperty.ANNOTATED_AS_GUARDED_BY_THIS); } if (threadSafe) { propertySet.addProperty(InconsistentSyncWarningProperty.ANNOTATED_AS_THREAD_SAFE); } if (!guardedByThis && locked == 0) { continue;// propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_LOCKED); } if (DEBUG) { System.out.println("IS2: " + xfield); if (guardedByThis) System.out.println("Guarded by this"); System.out.println(" RL: " + numReadLocked); System.out.println(" WL: " + numWriteLocked); System.out.println(" RU: " + numReadUnlocked); System.out.println(" WU: " + numWriteUnlocked); } if (!EVAL && numReadUnlocked > 0 && ((int) (UNSYNC_FACTOR * biasedUnlocked)) > biasedLocked) {// continue; propertySet.addProperty(InconsistentSyncWarningProperty.MANY_BIASED_UNLOCKED); } // NOTE: we ignore access to public, volatile, and final fields if (numWriteUnlocked + numWriteLocked == 0) { // No writes outside of constructor if (DEBUG) System.out.println(" No writes outside of constructor"); propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_WRITTEN);// continue; } if (numReadUnlocked + numReadLocked == 0) { // No reads outside of constructor if (DEBUG) System.out.println(" No reads outside of constructor");// continue; propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_READ); } if (stats.getNumLocalLocks() == 0) { if (DEBUG) System.out.println(" No local locks");// continue; propertySet.addProperty(InconsistentSyncWarningProperty.NO_LOCAL_LOCKS); } int freq; if (locked + unlocked > 0) { freq = (100 * locked) / (locked + unlocked); } else { freq = 0; } if (freq < MIN_SYNC_PERCENT) {// continue; propertySet.addProperty(InconsistentSyncWarningProperty.BELOW_MIN_SYNC_PERCENT); } if (DEBUG) System.out.println(" Sync %: " + freq); if (stats.getNumGetterMethodAccesses() >= unlocked) { // Unlocked accesses are only in getter method(s). propertySet.addProperty(InconsistentSyncWarningProperty.ONLY_UNSYNC_IN_GETTERS); } // At this point, we report the field as being inconsistently synchronized int priority = propertySet.computePriority(NORMAL_PRIORITY); if (!propertySet.isFalsePositive(priority)) { BugInstance bugInstance = new BugInstance(this, guardedByThis? "IS_FIELD_NOT_GUARDED" : "IS2_INCONSISTENT_SYNC", priority) .addClass(xfield.getClassName()) .addField(xfield) .addInt(freq).describe(IntAnnotation.INT_SYNC_PERCENT); if (FindBugsAnalysisFeatures.isRelaxedMode()) { propertySet.decorateBugInstance(bugInstance); } // Add source lines for unsynchronized accesses for (Iterator<SourceLineAnnotation> j = stats.unsyncAccessIterator(); j.hasNext();) { SourceLineAnnotation accessSourceLine = j.next(); bugInstance.addSourceLine(accessSourceLine).describe("SOURCE_LINE_UNSYNC_ACCESS"); } if (SYNC_ACCESS) { // Add source line for synchronized accesses; // useful for figuring out what the detector is doing for (Iterator<SourceLineAnnotation> j = stats.syncAccessIterator(); j.hasNext();) { SourceLineAnnotation accessSourceLine = j.next(); bugInstance.addSourceLine(accessSourceLine).describe("SOURCE_LINE_SYNC_ACCESS"); } } if (EVAL) { bugInstance.addInt(biasedLocked).describe("INT_BIASED_LOCKED"); bugInstance.addInt(biasedUnlocked).describe("INT_BIASED_UNLOCKED"); } bugReporter.reportBug(bugInstance); } } } /* ---------------------------------------------------------------------- * Implementation * ---------------------------------------------------------------------- */ private static boolean isConstructor(String methodName) { return methodName.equals("<init>") || methodName.equals("<clinit>") || methodName.equals("readObject") || methodName.equals("clone") || methodName.equals("close") || methodName.equals("writeObject") || methodName.equals("toString") || methodName.equals("init") || methodName.equals("initialize") || methodName.equals("dispose") || methodName.equals("finalize") || methodName.equals("this"); } private void analyzeMethod(ClassContext classContext, Method method, Set<Method> lockedMethodSet) throws CFGBuilderException, DataflowAnalysisException { InnerClassAccessMap icam = AnalysisContext.currentAnalysisContext().getInnerClassAccessMap(); ConstantPoolGen cpg = classContext.getConstantPoolGen(); MethodGen methodGen = classContext.getMethodGen(method); if (methodGen == null) return; CFG cfg = classContext.getCFG(method); LockChecker lockChecker = classContext.getLockChecker(method); ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method); boolean isGetterMethod = isGetterMethod(classContext, method); if (DEBUG) System.out.println("**** Analyzing method " + SignatureConverter.convertMethodSignature(methodGen)); for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) { Location location = i.next(); try { Instruction ins = location.getHandle().getInstruction(); XField xfield = null; boolean isWrite = false; boolean isLocal = false; if (ins instanceof FieldInstruction) { FieldInstruction fins = (FieldInstruction) ins; xfield = Hierarchy.findXField(fins, cpg); isWrite = ins.getOpcode() == Constants.PUTFIELD; isLocal = fins.getClassName(cpg).equals(classContext.getJavaClass().getClassName()); if (DEBUG) System.out.println("Handling field access: " + location.getHandle() + " (frame=" + vnaDataflow.getFactAtLocation(location) + ")"); } else if (ins instanceof INVOKESTATIC) { INVOKESTATIC inv = (INVOKESTATIC) ins; InnerClassAccess access = icam.getInnerClassAccess(inv, cpg); if (access != null && access.getMethodSignature().equals(inv.getSignature(cpg))) { xfield = access.getField(); isWrite = !access.isLoad(); isLocal = false; if (DEBUG) System.out.println("Handling inner class access: " + location.getHandle() +
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -