/* * FindBugs - Find bugs in Java programs * Copyright (C) 2003,2004 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.Map; import java.util.Set; import java.util.TreeMap; import org.apache.bcel.classfile.Code; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.Method; 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.IntAnnotation; import edu.umd.cs.findbugs.SystemProperties; public class LockedFields extends BytecodeScanningDetector { private static final boolean DEBUG = SystemProperties.getBoolean("lockedfields.debug"); Set<FieldAnnotation> volatileOrFinalFields = new HashSet<FieldAnnotation>(); Set<FieldAnnotation> fieldsWritten = new HashSet<FieldAnnotation>(); Set<FieldAnnotation> fieldsRead = new HashSet<FieldAnnotation>(); Set<FieldAnnotation> localLocks = new HashSet<FieldAnnotation>(); Set<FieldAnnotation> publicFields = new HashSet<FieldAnnotation>(); Set<FieldAnnotation> writtenOutsideOfConstructor = new HashSet<FieldAnnotation>(); boolean synchronizedMethod; boolean publicMethod; boolean protectedMethod; // boolean privateMethod; boolean inConstructor; Map<FieldAnnotation, int[]> stats = new TreeMap<FieldAnnotation, int[]>(); int state; boolean thisOnTopOfStack; boolean doubleThisOnTopOfStack; boolean thisLocked; boolean thisLockingOnly = true; private BugReporter bugReporter; static final int READ_LOCKED = 0; static final int WRITTEN_LOCKED = 1; static final int READ_UNLOCKED = 2; static final int WRITTEN_UNLOCKED = 3; static final String[] names = { "R/L", "W/L", "R/U", "W/U"}; public LockedFields(BugReporter bugReporter) { this.bugReporter = bugReporter; } private void updateStats(Set<FieldAnnotation> fields, int mode) { // if (privateMethod) return; if (!publicMethod && !protectedMethod) { if (mode == READ_UNLOCKED || mode == WRITTEN_UNLOCKED) return; } /* if (!publicMethod) { if (mode == READ_UNLOCKED || mode == WRITTEN_UNLOCKED) return; } */ for (FieldAnnotation f : fields) { if (f.getClassName().equals(getDottedClassName()) && mode <= WRITTEN_LOCKED) localLocks.add(f); int[] theseStats = stats.get(f); if (theseStats == null) { theseStats = new int[4]; stats.put(f, theseStats); } if (DEBUG) System.out.println(names[mode] + " " + getFullyQualifiedMethodName() + " " + f.toString()); theseStats[mode]++; } } @Override public void visit(Field obj) { super.visit(obj); FieldAnnotation f = FieldAnnotation.fromVisitedField(this); int flags = obj.getAccessFlags(); boolean publicField = (flags & ACC_PUBLIC) != 0; boolean volatileField = (flags & ACC_VOLATILE) != 0; boolean finalField = (flags & ACC_FINAL) != 0; if (publicField) publicFields.add(f); if (volatileField || finalField) volatileOrFinalFields.add(f); } @Override public void visit(Method obj) { super.visit(obj); int flags = obj.getAccessFlags(); publicMethod = (flags & ACC_PUBLIC) != 0; protectedMethod = (flags & ACC_PROTECTED) != 0; synchronizedMethod = (flags & ACC_SYNCHRONIZED) != 0; if (synchronizedMethod) state = 1; else state = 0; fieldsWritten.clear(); fieldsRead.clear(); inConstructor = getMethodName().equals("<init>") || getMethodName().equals("<clinit>") || getMethodName().equals("readObject") || getMethodName().equals("clone") || getMethodName().equals("close") || getMethodName().equals("finalize"); /* privateMethod = (flags & ACC_PRIVATE) != 0 || methodName.startsWith("access$"); */ } @Override public void visit(Code obj) { if (inConstructor) return; thisOnTopOfStack = false; thisLocked = false; super.visit(obj); // System.out.println("End of method, state = " + state); if (state == 1) { updateStats(fieldsWritten, WRITTEN_LOCKED); updateStats(fieldsRead, READ_LOCKED); } else if (obj.getCode().length > 6) { updateStats(fieldsWritten, WRITTEN_UNLOCKED); updateStats(fieldsRead, READ_UNLOCKED); } } @Override public void sawOpcode(int seen) { // state: 0 - unlocked // state: 1 - locked // state: 2 - saw unlocked, but might still be locked switch (seen) { case ASTORE_1: case ASTORE_2: case ASTORE_3: case ASTORE: thisOnTopOfStack = doubleThisOnTopOfStack; return; case ALOAD_0: thisOnTopOfStack = true; return; case DUP: doubleThisOnTopOfStack = thisOnTopOfStack; return; case MONITOREXIT: if (thisLockingOnly && !thisLocked) break; updateStats(fieldsWritten, WRITTEN_LOCKED); updateStats(fieldsRead, READ_LOCKED); state = 2; // System.out.println("monitorexit " + thisLocked); fieldsWritten.clear(); fieldsRead.clear(); break; case MONITORENTER: thisLocked = thisOnTopOfStack; if (thisLockingOnly && !thisLocked) break; updateStats(fieldsWritten, WRITTEN_UNLOCKED); updateStats(fieldsRead, READ_UNLOCKED); // System.out.println("monitorenter " + thisLocked); state = 1; fieldsWritten.clear(); fieldsRead.clear(); break; case PUTFIELD: { FieldAnnotation f = FieldAnnotation.fromReferencedField(this); writtenOutsideOfConstructor.add(f); if (!getClassName().equals(getClassConstantOperand())) break; // System.out.println("putfield " + f + ", state = " + state); fieldsWritten.add(f); } break; case GETFIELD: int next = codeBytes[getPC() + 3] & 0xff; if (!thisOnTopOfStack) break; if (next != IFNULL && next != IFNONNULL) { FieldAnnotation f = FieldAnnotation.fromReferencedField(this); // System.out.println("getfield " + f); fieldsRead.add(f); /* System.out.println("After read of " + classConstant + "." + nameConstant + ", next PC is " + (PC+3) ); System.out.println("After read of " + classConstant + "." + nameConstant + ", next opcode is " + OPCODE_NAMES[next] + " (" + next + ")" ); */ } // OPCODE_NAMES break; } thisOnTopOfStack = false; doubleThisOnTopOfStack = false; } @Override public void report() { int noLocked = 0; int noUnlocked = 0; int isPublic = 0; int couldBeFinal = 0; int noLocalLocks = 0; int volatileOrFinalCount = 0; int mostlyUnlocked = 0; //for (Iterator<Map.Entry<FieldAnnotation, int[]>> i = stats.entrySet().iterator(); i.hasNext();) { for (FieldAnnotation f : stats.keySet()) { int[] theseStats = stats.get(f); int locked = theseStats[READ_LOCKED] + theseStats[WRITTEN_LOCKED]; int biasedLocked = theseStats[READ_LOCKED] + 2 * theseStats[WRITTEN_LOCKED]; int unlocked = theseStats[READ_UNLOCKED] + theseStats[WRITTEN_UNLOCKED]; int biasedUnlocked = theseStats[READ_UNLOCKED] + 2 * theseStats[WRITTEN_UNLOCKED]; int writes = theseStats[WRITTEN_LOCKED] + theseStats[WRITTEN_UNLOCKED]; if (locked == 0) { noLocked++; continue; } if (unlocked == 0) { noUnlocked++; continue; } if (theseStats[READ_UNLOCKED] > 0 && 2 * biasedUnlocked > biasedLocked) { if (DEBUG) System.out.println("Mostly unlocked for " + f + ":"); int freq = (100 * locked) / (locked + unlocked); if (DEBUG) { System.out.print(freq + " "); for (int j = 0; j < 4; j++) System.out.print(theseStats[j] + " "); System.out.println(f); } mostlyUnlocked++; continue; } if (publicFields.contains(f)) { isPublic++; continue; } if (volatileOrFinalFields.contains(f)) { volatileOrFinalCount++; continue; } if (!writtenOutsideOfConstructor.contains(f)) { couldBeFinal++; continue; } if (!localLocks.contains(f)) { if (DEBUG) System.out.println("No local locks of " + f); noLocalLocks++; continue; } int freq = (100 * locked) / (locked + unlocked); bugReporter.reportBug(new BugInstance(this, "IS_INCONSISTENT_SYNC", NORMAL_PRIORITY) .addClass(f.getClassName()) .addField(f) .addInt(freq).describe(IntAnnotation.INT_SYNC_PERCENT)); if (DEBUG) { System.out.print(freq + " "); for (int j = 0; j < 4; j++) System.out.print(theseStats[j] + " "); System.out.println(f); } } if (DEBUG) { int total = stats.size(); System.out.println(" Total fields: " + total); System.out.println(" No locked accesses: " + noLocked); System.out.println("No unlocked accesses: " + noUnlocked); System.out.println(" Mostly unlocked: " + mostlyUnlocked); System.out.println(" public fields: " + isPublic); if (couldBeFinal > 0) System.out.println(" could be Final: " + couldBeFinal); System.out.println(" volatile or final: " + volatileOrFinalCount); System.out.println(" no local locks: " + noLocalLocks); System.out.println(" questionable fields: " + (total - noLocked - noUnlocked - isPublic - volatileOrFinalCount - couldBeFinal - noLocalLocks - mostlyUnlocked)); } } }