/*
* 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));
}
}
}