KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > umd > cs > findbugs > detect > FindInconsistentSync2


1 /*
2  * FindBugs - Find bugs in Java programs
3  * Copyright (C) 2003-2005, University of Maryland
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */

19
20 package edu.umd.cs.findbugs.detect;
21
22
23 import edu.umd.cs.findbugs.*;
24 import edu.umd.cs.findbugs.ba.*;
25 import edu.umd.cs.findbugs.ba.type.*;
26 import edu.umd.cs.findbugs.ba.vna.*;
27 import edu.umd.cs.findbugs.props.WarningPropertySet;
28 import java.util.*;
29 import org.apache.bcel.Constants;
30 import org.apache.bcel.classfile.*;
31 import org.apache.bcel.generic.*;
32
33 /**
34  * Find instance fields which are sometimes accessed (read or written)
35  * with the receiver lock held and sometimes without.
36  * These are candidates to be data races.
37  *
38  * @author David Hovemeyer
39  * @author Bill Pugh
40  */

41 public class FindInconsistentSync2 implements Detector {
42     private static final boolean DEBUG = SystemProperties.getBoolean("fis.debug");
43     private static final boolean SYNC_ACCESS = true;
44     // Boolean.getBoolean("fis.syncAccess");
45
private static final boolean ADJUST_SUBCLASS_ACCESSES = !SystemProperties.getBoolean("fis.noAdjustSubclass");
46     private static final boolean EVAL = SystemProperties.getBoolean("fis.eval");
47
48     /* ----------------------------------------------------------------------
49      * Tuning parameters
50      * ---------------------------------------------------------------------- */

51
52     /**
53      * Minimum percent of unbiased field accesses that must be synchronized in
54      * order to report a field as inconsistently synchronized.
55      * This is meant to distinguish incidental synchronization from
56      * intentional synchronization.
57      */

58     private static final int MIN_SYNC_PERCENT =
59             SystemProperties.getInteger("findbugs.fis.minSyncPercent", 50).intValue();
60
61     /**
62      * Bias that writes are given with respect to reads.
63      * The idea is that this should be above 1.0, because unsynchronized
64      * writes are more dangerous than unsynchronized reads.
65      */

66     private static final double WRITE_BIAS =
67             Double.parseDouble(SystemProperties.getProperty("findbugs.fis.writeBias", "2.0"));
68
69     /**
70      * Factor which the biased number of unsynchronized accesses is multiplied by.
71      * I.e., for factor <i>f</i>, if <i>nUnsync</i> is the biased number of unsynchronized
72      * accesses, and <i>nSync</i> is the biased number of synchronized accesses,
73      * and
74      * <pre>
75      * <i>f</i>(<i>nUnsync</i>) &gt; <i>nSync</i>
76      * </pre>
77      * then we report a bug. Default value is 2.0, which means that we
78      * report a bug if more than 1/3 of accesses are unsynchronized.
79      * <p/>
80      * <p> Note that <code>MIN_SYNC_PERCENT</code> also influences
81      * whether we report a bug: it specifies the minimum unbiased percentage
82      * of synchronized accesses.
83      */

84     private static final double UNSYNC_FACTOR =
85             Double.parseDouble(SystemProperties.getProperty("findbugs.fis.unsyncFactor", "2.0"));
86
87     /* ----------------------------------------------------------------------
88      * Helper classes
89      * ---------------------------------------------------------------------- */

90
91     private static final int UNLOCKED = 0;
92     private static final int LOCKED = 1;
93     private static final int READ = 0;
94     private static final int WRITE = 2;
95
96     private static final int READ_UNLOCKED = READ | UNLOCKED;
97     private static final int WRITE_UNLOCKED = WRITE | UNLOCKED;
98     private static final int READ_LOCKED = READ | LOCKED;
99     private static final int WRITE_LOCKED = WRITE | LOCKED;
100
101     /**
102      * The access statistics for a field.
103      * Stores the number of locked and unlocked reads and writes,
104      * as well as the number of accesses made with a lock held.
105      */

106     private static class FieldStats {
107         private int[] countList = new int[4];
108         private int numLocalLocks = 0;
109         private int numGetterMethodAccesses = 0;
110         private LinkedList<SourceLineAnnotation> unsyncAccessList = new LinkedList<SourceLineAnnotation>();
111         private LinkedList<SourceLineAnnotation> syncAccessList = new LinkedList<SourceLineAnnotation>();
112
113         public void addAccess(int kind) {
114             countList[kind]++;
115         }
116
117         public int getNumAccesses(int kind) {
118             return countList[kind];
119         }
120
121         public void addLocalLock() {
122             numLocalLocks++;
123         }
124
125         public int getNumLocalLocks() {
126             return numLocalLocks;
127         }
128
129         public void addGetterMethodAccess() {
130             numGetterMethodAccesses++;
131         }
132
133         public int getNumGetterMethodAccesses() {
134             return numGetterMethodAccesses;
135         }
136
137         public void addAccess(ClassContext classContext, Method method, InstructionHandle handle, boolean isLocked) {
138             if (!SYNC_ACCESS && isLocked)
139                 return;
140
141             JavaClass javaClass = classContext.getJavaClass();
142             String JavaDoc sourceFile = javaClass.getSourceFileName();
143             MethodGen methodGen = classContext.getMethodGen(method);
144             SourceLineAnnotation accessSourceLine = SourceLineAnnotation.fromVisitedInstruction(
145                     classContext, methodGen, sourceFile, handle);
146             if (accessSourceLine != null)
147                 (isLocked ? syncAccessList : unsyncAccessList).add(accessSourceLine);
148         }
149
150         public Iterator<SourceLineAnnotation> unsyncAccessIterator() {
151             return unsyncAccessList.iterator();
152         }
153
154         public Iterator<SourceLineAnnotation> syncAccessIterator() {
155             return syncAccessList.iterator();
156         }
157     }
158
159     /* ----------------------------------------------------------------------
160      * Fields
161      * ---------------------------------------------------------------------- */

162
163     private BugReporter bugReporter;
164     private Map<XField, FieldStats> statMap = new HashMap<XField, FieldStats>();
165
166     /* ----------------------------------------------------------------------
167      * Public methods
168      * ---------------------------------------------------------------------- */

169
170     public FindInconsistentSync2(BugReporter bugReporter) {
171         this.bugReporter = bugReporter;
172     }
173
174     public void visitClassContext(ClassContext classContext) {
175         JavaClass javaClass = classContext.getJavaClass();
176         if (DEBUG) System.out.println("******** Analyzing class " + javaClass.getClassName());
177         
178         // Build self-call graph
179
SelfCalls selfCalls = new SelfCalls(classContext) {
180             @Override JavaDoc
181                          public boolean wantCallsFor(Method method) {
182                 return !method.isPublic();
183             }
184         };
185         
186         Set<Method> lockedMethodSet;
187         Set<Method> publicReachableMethods;
188         
189         try {
190             selfCalls.execute();
191             CallGraph callGraph = selfCalls.getCallGraph();
192             if (DEBUG)
193                 System.out.println("Call graph (not unlocked methods): " + callGraph.getNumVertices() + " nodes, " +
194                         callGraph.getNumEdges() + " edges");
195             // Find call edges that are obviously locked
196
Set<CallSite> obviouslyLockedSites = findObviouslyLockedCallSites(classContext, selfCalls);
197             lockedMethodSet = findNotUnlockedMethods(classContext, selfCalls, obviouslyLockedSites);
198             lockedMethodSet.retainAll(findLockedMethods(classContext, selfCalls, obviouslyLockedSites));
199             publicReachableMethods = findPublicReachableMethods(classContext, selfCalls);
200         } catch (CFGBuilderException e) {
201             bugReporter.logError("Error finding locked call sites", e);
202             return;
203         } catch (DataflowAnalysisException e) {
204             bugReporter.logError("Error finding locked call sites", e);
205             return;
206         }
207
208         for (Method method : publicReachableMethods) {
209             if (classContext.getMethodGen(method) == null)
210                 continue;
211
212             /*
213                                              if (isConstructor(method.getName()))
214                                              continue;
215                                              */

216             if (method.getName().startsWith("access$"))
217                 // Ignore inner class access methods;
218
// we will treat calls to them as field accesses
219
continue;
220             try {
221                 analyzeMethod(classContext, method, lockedMethodSet);
222             } catch (CFGBuilderException e) {
223                 bugReporter.logError("Error analyzing method", e);
224             } catch (DataflowAnalysisException e) {
225                 bugReporter.logError("Error analyzing method", e);
226             }
227         }
228     }
229
230     public void report() {
231         for (XField xfield : statMap.keySet()) {
232             FieldStats stats = statMap.get(xfield);
233             JCIPAnnotationDatabase jcipAnotationDatabase = AnalysisContext.currentAnalysisContext()
234                                 .getJCIPAnnotationDatabase();
235             boolean guardedByThis = "this".equals(jcipAnotationDatabase.getFieldAnnotation(xfield, "GuardedBy"));
236             boolean notThreadSafe = jcipAnotationDatabase.hasClassAnnotation(xfield.getClassName(), "NotThreadSafe");
237             boolean threadSafe = jcipAnotationDatabase.hasClassAnnotation(xfield.getClassName().replace('/','.'), "ThreadSafe");
238             if (notThreadSafe) continue;
239             
240             WarningPropertySet propertySet = new WarningPropertySet();
241
242             int numReadUnlocked = stats.getNumAccesses(READ_UNLOCKED);
243             int numWriteUnlocked = stats.getNumAccesses(WRITE_UNLOCKED);
244             int numReadLocked = stats.getNumAccesses(READ_LOCKED);
245             int numWriteLocked = stats.getNumAccesses(WRITE_LOCKED);
246
247             int locked = numReadLocked + numWriteLocked;
248             int biasedLocked = numReadLocked + (int) (WRITE_BIAS * numWriteLocked);
249             int unlocked = numReadUnlocked + numWriteUnlocked;
250             int biasedUnlocked = numReadUnlocked + (int) (WRITE_BIAS * numWriteUnlocked);
251             int writes = numWriteLocked + numWriteUnlocked;
252
253             if (unlocked == 0) {
254                 continue;
255 // propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_UNLOCKED);
256
}
257
258             
259             if (guardedByThis) {
260                     propertySet.addProperty(InconsistentSyncWarningProperty.ANNOTATED_AS_GUARDED_BY_THIS);
261             
262             }
263             
264             if (threadSafe) {
265                 propertySet.addProperty(InconsistentSyncWarningProperty.ANNOTATED_AS_THREAD_SAFE);
266         
267         }
268             if (!guardedByThis && locked == 0) {
269                 continue;
270 // propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_LOCKED);
271
}
272
273             if (DEBUG) {
274                 System.out.println("IS2: " + xfield);
275                 if (guardedByThis) System.out.println("Guarded by this");
276                 System.out.println(" RL: " + numReadLocked);
277                 System.out.println(" WL: " + numWriteLocked);
278                 System.out.println(" RU: " + numReadUnlocked);
279                 System.out.println(" WU: " + numWriteUnlocked);
280             }
281             if (!EVAL && numReadUnlocked > 0 && ((int) (UNSYNC_FACTOR * biasedUnlocked)) > biasedLocked) {
282 // continue;
283
propertySet.addProperty(InconsistentSyncWarningProperty.MANY_BIASED_UNLOCKED);
284             }
285
286             // NOTE: we ignore access to public, volatile, and final fields
287

288             if (numWriteUnlocked + numWriteLocked == 0) {
289                 // No writes outside of constructor
290
if (DEBUG) System.out.println(" No writes outside of constructor");
291                 propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_WRITTEN);
292 // continue;
293
}
294
295             if (numReadUnlocked + numReadLocked == 0) {
296                 // No reads outside of constructor
297
if (DEBUG) System.out.println(" No reads outside of constructor");
298 // continue;
299
propertySet.addProperty(InconsistentSyncWarningProperty.NEVER_READ);
300             }
301
302             if (stats.getNumLocalLocks() == 0) {
303                 if (DEBUG) System.out.println(" No local locks");
304 // continue;
305
propertySet.addProperty(InconsistentSyncWarningProperty.NO_LOCAL_LOCKS);
306             }
307
308             int freq;
309             if (locked + unlocked > 0) {
310                 freq = (100 * locked) / (locked + unlocked);
311             } else {
312                 freq = 0;
313             }
314             if (freq < MIN_SYNC_PERCENT) {
315 // continue;
316
propertySet.addProperty(InconsistentSyncWarningProperty.BELOW_MIN_SYNC_PERCENT);
317             }
318             if (DEBUG) System.out.println(" Sync %: " + freq);
319
320             if (stats.getNumGetterMethodAccesses() >= unlocked) {
321                 // Unlocked accesses are only in getter method(s).
322
propertySet.addProperty(InconsistentSyncWarningProperty.ONLY_UNSYNC_IN_GETTERS);
323             }
324
325             // At this point, we report the field as being inconsistently synchronized
326
int priority = propertySet.computePriority(NORMAL_PRIORITY);
327             if (!propertySet.isFalsePositive(priority)) {
328                 BugInstance bugInstance = new BugInstance(guardedByThis? "IS_FIELD_NOT_GUARDED" : "IS2_INCONSISTENT_SYNC", priority)
329                         .addClass(xfield.getClassName())
330                         .addField(xfield)
331                         .addInt(freq).describe(IntAnnotation.INT_SYNC_PERCENT);
332
333                 if (FindBugsAnalysisFeatures.isRelaxedMode()) {
334                     propertySet.decorateBugInstance(bugInstance);
335                 }
336
337                 // Add source lines for unsynchronized accesses
338
for (Iterator<SourceLineAnnotation> j = stats.unsyncAccessIterator(); j.hasNext();) {
339                     SourceLineAnnotation accessSourceLine = j.next();
340                     bugInstance.addSourceLine(accessSourceLine).describe("SOURCE_LINE_UNSYNC_ACCESS");
341                 }
342
343                 if (SYNC_ACCESS) {
344                     // Add source line for synchronized accesses;
345
// useful for figuring out what the detector is doing
346
for (Iterator<SourceLineAnnotation> j = stats.syncAccessIterator(); j.hasNext();) {
347                         SourceLineAnnotation accessSourceLine = j.next();
348                         bugInstance.addSourceLine(accessSourceLine).describe("SOURCE_LINE_SYNC_ACCESS");
349                     }
350                 }
351
352                 if (EVAL) {
353                     bugInstance.addInt(biasedLocked).describe("INT_BIASED_LOCKED");
354                     bugInstance.addInt(biasedUnlocked).describe("INT_BIASED_UNLOCKED");
355                 }
356
357                 bugReporter.reportBug(bugInstance);
358             }
359         }
360     }
361
362     /* ----------------------------------------------------------------------
363      * Implementation
364      * ---------------------------------------------------------------------- */

365
366     private static boolean isConstructor(String JavaDoc methodName) {
367         return methodName.equals("<init>")
368                 || methodName.equals("<clinit>")
369                 || methodName.equals("readObject")
370                 || methodName.equals("clone")
371                 || methodName.equals("close")
372                 || methodName.equals("writeObject")
373                 || methodName.equals("toString")
374                 || methodName.equals("init")
375                 || methodName.equals("initialize")
376                 || methodName.equals("dispose")
377                 || methodName.equals("finalize")
378                 || methodName.equals("this");
379     }
380
381     private void analyzeMethod(ClassContext classContext, Method method, Set<Method> lockedMethodSet)
382             throws CFGBuilderException, DataflowAnalysisException {
383
384         InnerClassAccessMap icam = AnalysisContext.currentAnalysisContext().getInnerClassAccessMap();
385         ConstantPoolGen cpg = classContext.getConstantPoolGen();
386         MethodGen methodGen = classContext.getMethodGen(method);
387         if (methodGen == null) return;
388         CFG cfg = classContext.getCFG(method);
389         LockChecker lockChecker = classContext.getLockChecker(method);
390         ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
391         boolean isGetterMethod = isGetterMethod(classContext, method);
392
393         if (DEBUG)
394             System.out.println("**** Analyzing method " +
395                     SignatureConverter.convertMethodSignature(methodGen));
396
397         for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
398             Location location = i.next();
399             try {
400                 Instruction ins = location.getHandle().getInstruction();
401                 XField xfield = null;
402                 boolean isWrite = false;
403                 boolean isLocal = false;
404
405                 if (ins instanceof FieldInstruction) {
406                     FieldInstruction fins = (FieldInstruction) ins;
407                     xfield = Hierarchy.findXField(fins, cpg);
408                     isWrite = ins.getOpcode() == Constants.PUTFIELD;
409                     isLocal = fins.getClassName(cpg).equals(classContext.getJavaClass().getClassName());
410                     if (DEBUG)
411                         System.out.println("Handling field access: " + location.getHandle() +
412                                 " (frame=" + vnaDataflow.getFactAtLocation(location) + ")");
413                 } else if (ins instanceof INVOKESTATIC) {
414                     INVOKESTATIC inv = (INVOKESTATIC) ins;
415                     InnerClassAccess access = icam.getInnerClassAccess(inv, cpg);
416                     if (access != null && access.getMethodSignature().equals(inv.getSignature(cpg))) {
417                         xfield = access.getField();
418                         isWrite = !access.isLoad();
419                         isLocal = false;
420                         if (DEBUG)
421                             System.out.println("Handling inner class access: " + location.getHandle() +
422                                     " (frame=" + vnaDataflow.getFactAtLocation(location) + ")");
423                     }
424                 }
425
426                 if (xfield == null)
427                     continue;
428
429                 // We only care about mutable nonvolatile nonpublic instance fields.
430
if (xfield.isStatic() || xfield.isPublic() || xfield.isVolatile() || xfield.isFinal())
431                     continue;
432
433                 // The value number frame could be invalid if the basic
434
// block became unreachable due to edge pruning (dead code).
435
ValueNumberFrame frame = vnaDataflow.getFactAtLocation(location);
436                 if (!frame.isValid())
437                     continue;
438
439                 // Get lock set and instance value
440
ValueNumber thisValue = !method.isStatic() ? vnaDataflow.getAnalysis().getThisValue() : null;
441                 LockSet lockSet = lockChecker.getFactAtLocation(location);
442                 InstructionHandle handle = location.getHandle();
443                 ValueNumber instance = frame.getInstance(handle.getInstruction(), cpg);
444                 if (DEBUG) {
445                     System.out.println("Lock set: " + lockSet);
446                     System.out.println("value number: " + instance.getNumber());
447                     System.out.println("Lock count: " + lockSet.getLockCount(instance.getNumber()));
448                 }
449                 
450                 
451                 // Is the instance locked?
452
// We consider the access to be locked if either
453
// - the object is explicitly locked, or
454
// - the field is accessed through the "this" reference,
455
// and the method is in the locked method set, or
456
// - any value returned by a called method is locked;
457
// the (conservative) assumption is that the return lock object
458
// is correct for synchronizing the access
459
boolean isExplicitlyLocked = lockSet.getLockCount(instance.getNumber()) > 0;
460                 boolean isAccessedThroughThis = thisValue != null && thisValue.equals(instance);
461                 boolean isLocked = isExplicitlyLocked
462                         || (lockedMethodSet.contains(method) && isAccessedThroughThis)
463                         || lockSet.containsReturnValue(vnaDataflow.getAnalysis().getFactory());
464
465                 // Adjust the field so its class name is the same
466
// as the type of reference it is accessed through.
467
// This helps fix false positives produced when a
468
// threadsafe class is extended by a subclass that
469
// doesn't care about thread safety.
470
if (ADJUST_SUBCLASS_ACCESSES) {
471                     // Find the type of the object instance
472
TypeDataflow typeDataflow = classContext.getTypeDataflow(method);
473                     TypeFrame typeFrame = typeDataflow.getFactAtLocation(location);
474                     if (!typeFrame.isValid()) continue;
475                     Type instanceType = typeFrame.getInstance(handle.getInstruction(), cpg);
476                     if (instanceType instanceof TopType) {
477                         if (DEBUG) System.out.println("Freaky: typeFrame is " + typeFrame);
478                         continue;
479                     }
480                     // Note: instance type can be Null,
481
// in which case we won't adjust the field type.
482
if (instanceType != TypeFrame.getNullType()) {
483                         if (!(instanceType instanceof ObjectType)) {
484                             throw new DataflowAnalysisException("Field accessed through non-object reference " + instanceType,
485                                     methodGen, handle);
486                         }
487                         ObjectType objType = (ObjectType) instanceType;
488
489                         // If instance class name is not the same as that of the field,
490
// make it so
491
String JavaDoc instanceClassName = objType.getClassName();
492                         if (!instanceClassName.equals(xfield.getClassName())) {
493                             xfield = new InstanceField(instanceClassName,
494                                     xfield.getName(),
495                                     xfield.getSignature(),
496                                     xfield.getAccessFlags());
497                         }
498                     }
499                 }
500
501                 int kind = 0;
502                 kind |= isLocked ? LOCKED : UNLOCKED;
503                 kind |= isWrite ? WRITE : READ;
504
505                 if (isLocked || !isConstructor(method.getName())) {
506                     if (DEBUG)
507                         System.out.println("IS2:\t" +
508                                 SignatureConverter.convertMethodSignature(methodGen) +
509                                 "\t" + xfield + "\t" + ((isWrite ? "W" : "R") + "/" + (isLocked ? "L" : "U")));
510
511                     FieldStats stats = getStats(xfield);
512                     stats.addAccess(kind);
513
514                     if (isExplicitlyLocked && isLocal)
515                         stats.addLocalLock();
516
517                     if (isGetterMethod && !isLocked)
518                         stats.addGetterMethodAccess();
519
520                     stats.addAccess(classContext, method, handle, isLocked);
521                 }
522             } catch (ClassNotFoundException JavaDoc e) {
523                 bugReporter.reportMissingClass(e);
524             }
525         }
526     }
527
528     /**
529      * Determine whether or not the the given method is
530      * a getter method. I.e., if it just returns the
531      * value of an instance field.
532      *
533      * @param classContext the ClassContext for the class containing the method
534      * @param method the method
535      */

536     public static boolean isGetterMethod(ClassContext classContext, Method method) {
537         MethodGen methodGen = classContext.getMethodGen(method);
538         if (methodGen == null) return false;
539         InstructionList il = methodGen.getInstructionList();
540         // System.out.println("Checking getter method: " + method.getName());
541
if (il.getLength() > 60)
542             return false;
543
544         int count = 0;
545         Iterator<InstructionHandle> it = il.iterator();
546         while (it.hasNext()) {
547             InstructionHandle ih = it.next();
548             switch (ih.getInstruction().getOpcode()) {
549             case Constants.GETFIELD:
550                 count++;
551                 if (count > 1) return false;
552                 break;
553             case Constants.PUTFIELD:
554             case Constants.BALOAD:
555             case Constants.CALOAD:
556             case Constants.DALOAD:
557             case Constants.FALOAD:
558             case Constants.IALOAD:
559             case Constants.LALOAD:
560             case Constants.SALOAD:
561             case Constants.AALOAD:
562             case Constants.BASTORE:
563             case Constants.CASTORE:
564             case Constants.DASTORE:
565             case Constants.FASTORE:
566             case Constants.IASTORE:
567             case Constants.LASTORE:
568             case Constants.SASTORE:
569             case Constants.AASTORE:
570             case Constants.PUTSTATIC:
571                 return false;
572             case Constants.INVOKESTATIC:
573             case Constants.INVOKEVIRTUAL:
574             case Constants.INVOKEINTERFACE:
575             case Constants.INVOKESPECIAL:
576             case Constants.GETSTATIC:
577                 // no-op
578

579             }
580         }
581         // System.out.println("Found getter method: " + method.getName());
582
return true;
583     }
584
585     /**
586      * Get the access statistics for given field.
587      */

588     private FieldStats getStats(XField field) {
589         FieldStats stats = statMap.get(field);
590         if (stats == null) {
591             stats = new FieldStats();
592             statMap.put(field, stats);
593         }
594         return stats;
595     }
596
597     /**
598      * Find methods that appear to never be called from an unlocked context
599      * We assume that nonpublic methods will only be called from
600      * within the class, which is not really a valid assumption.
601      */

602     private Set<Method> findNotUnlockedMethods(ClassContext classContext, SelfCalls selfCalls,
603                                                Set<CallSite> obviouslyLockedSites)
604             throws CFGBuilderException, DataflowAnalysisException {
605
606         JavaClass javaClass = classContext.getJavaClass();
607         Method[] methodList = javaClass.getMethods();
608
609         CallGraph callGraph = selfCalls.getCallGraph();
610
611         // Initially, assume no methods are called from an
612
// unlocked context
613
Set<Method> lockedMethodSet = new HashSet<Method>();
614         lockedMethodSet.addAll(Arrays.asList(methodList));
615
616         // Assume all public methods are called from
617
// unlocked context
618
for (Method method : methodList) {
619             if (method.isPublic()
620                     && !isConstructor(method.getName())) {
621                 lockedMethodSet.remove(method);
622             }
623         }
624
625         // Explore the self-call graph to find nonpublic methods
626
// that can be called from an unlocked context.
627
boolean change;
628         do {
629             change = false;
630
631             for (Iterator<CallGraphEdge> i = callGraph.edgeIterator(); i.hasNext();) {
632                 CallGraphEdge edge = i.next();
633                 CallSite callSite = edge.getCallSite();
634
635                 // Ignore obviously locked edges
636
if (obviouslyLockedSites.contains(callSite))
637                     continue;
638
639                 // If the calling method is locked, ignore the edge
640
if (lockedMethodSet.contains(callSite.getMethod()))
641                     continue;
642
643                 // Calling method is unlocked, so the called method
644
// is also unlocked.
645
CallGraphNode target = edge.getTarget();
646                 if (lockedMethodSet.remove(target.getMethod()))
647                     change = true;
648             }
649         } while (change);
650
651         if (DEBUG) {
652             System.out.println("Apparently not unlocked methods:");
653             for (Method method : lockedMethodSet) {
654                 System.out.println("\t" + method.getName());
655             }
656         }
657
658         // We assume that any methods left in the locked set
659
// are called only from a locked context.
660
return lockedMethodSet;
661     }
662
663     /**
664      * Find methods that appear to always be called from a locked context.
665      * We assume that nonpublic methods will only be called from
666      * within the class, which is not really a valid assumption.
667      */

668     private Set<Method> findLockedMethods(ClassContext classContext, SelfCalls selfCalls,
669                                           Set<CallSite> obviouslyLockedSites)
670             throws CFGBuilderException, DataflowAnalysisException {
671
672         JavaClass javaClass = classContext.getJavaClass();
673         Method[] methodList = javaClass.getMethods();
674
675         CallGraph callGraph = selfCalls.getCallGraph();
676
677         // Initially, assume all methods are locked
678
Set<Method> lockedMethodSet = new HashSet<Method>();
679
680         // Assume all public methods are unlocked
681
for (Method method : methodList) {
682             if (method.isSynchronized()) {
683                 lockedMethodSet.add(method);
684             }
685         }
686
687         // Explore the self-call graph to find nonpublic methods
688
// that can be called from an unlocked context.
689
boolean change;
690         do {
691             change = false;
692
693             for (Iterator<CallGraphEdge> i = callGraph.edgeIterator(); i.hasNext();) {
694                 CallGraphEdge edge = i.next();
695                 CallSite callSite = edge.getCallSite();
696
697                 // Ignore obviously locked edges
698
// If the calling method is locked, ignore the edge
699
if (obviouslyLockedSites.contains(callSite)
700                         || lockedMethodSet.contains(callSite.getMethod())) {
701                     // Calling method is unlocked, so the called method
702
// is also unlocked.
703
CallGraphNode target = edge.getTarget();
704                     if (lockedMethodSet.add(target.getMethod()))
705                         change = true;
706                 }
707             }
708         } while (change);
709
710         if (DEBUG) {
711             System.out.println("Apparently locked methods:");
712             for (Method method : lockedMethodSet) {
713                 System.out.println("\t" + method.getName());
714             }
715         }
716
717         // We assume that any methods left in the locked set
718
// are called only from a locked context.
719
return lockedMethodSet;
720     }
721
722     /**
723      * Find methods that do not appear to be reachable from public methods.
724      * Such methods will not be analyzed.
725      */

726     private Set<Method> findPublicReachableMethods(ClassContext classContext, SelfCalls selfCalls)
727             throws CFGBuilderException, DataflowAnalysisException {
728
729         JavaClass javaClass = classContext.getJavaClass();
730         Method[] methodList = javaClass.getMethods();
731
732         CallGraph callGraph = selfCalls.getCallGraph();
733
734         // Initially, assume all methods are locked
735
Set<Method> publicReachableMethodSet = new HashSet<Method>();
736
737         // Assume all public methods are unlocked
738
for (Method method : methodList) {
739             if (method.isPublic()
740                     && !isConstructor(method.getName())) {
741                 publicReachableMethodSet.add(method);
742             }
743         }
744
745         // Explore the self-call graph to find nonpublic methods
746
// that can be called from an unlocked context.
747
boolean change;
748         do {
749             change = false;
750
751             for (Iterator<CallGraphEdge> i = callGraph.edgeIterator(); i.hasNext();) {
752                 CallGraphEdge edge = i.next();
753                 CallSite callSite = edge.getCallSite();
754
755                 // Ignore obviously locked edges
756
// If the calling method is locked, ignore the edge
757
if (publicReachableMethodSet.contains(callSite.getMethod())) {
758                     // Calling method is reachable, so the called method
759
// is also reachable.
760
CallGraphNode target = edge.getTarget();
761                     if (publicReachableMethodSet.add(target.getMethod()))
762                         change = true;
763                 }
764             }
765         } while (change);
766
767         if (DEBUG) {
768             System.out.println("Methods apparently reachable from public non-constructor methods:");
769             for (Method method : publicReachableMethodSet) {
770                 System.out.println("\t" + method.getName());
771             }
772         }
773
774         return publicReachableMethodSet;
775     }
776
777     /**
778      * Find all self-call sites that are obviously locked.
779      */

780     private Set<CallSite> findObviouslyLockedCallSites(ClassContext classContext, SelfCalls selfCalls)
781             throws CFGBuilderException, DataflowAnalysisException {
782         ConstantPoolGen cpg = classContext.getConstantPoolGen();
783
784         // Find all obviously locked call sites
785
Set<CallSite> obviouslyLockedSites = new HashSet<CallSite>();
786         for (Iterator<CallSite> i = selfCalls.callSiteIterator(); i.hasNext();) {
787             CallSite callSite = i.next();
788             Method method = callSite.getMethod();
789             Location location = callSite.getLocation();
790             InstructionHandle handle = location.getHandle();
791
792             // Only instance method calls qualify as candidates for
793
// "obviously locked"
794
Instruction ins = handle.getInstruction();
795             if (ins.getOpcode() == Constants.INVOKESTATIC)
796                 continue;
797
798             // Get lock set for site
799
LockChecker lockChecker = classContext.getLockChecker(method);
800             LockSet lockSet = lockChecker.getFactAtLocation(location);
801
802             // Get value number frame for site
803
ValueNumberDataflow vnaDataflow = classContext.getValueNumberDataflow(method);
804             ValueNumberFrame frame = vnaDataflow.getFactAtLocation(location);
805
806             // NOTE: if the CFG on which the value number analysis was performed
807
// was pruned, there may be unreachable instructions. Therefore,
808
// we can't assume the frame is valid.
809
if (!frame.isValid())
810                 continue;
811
812             // Find the ValueNumber of the receiver object
813
int numConsumed = ins.consumeStack(cpg);
814             MethodGen methodGen = classContext.getMethodGen(method);
815             assert methodGen != null;
816             if (numConsumed == Constants.UNPREDICTABLE)
817                 throw new DataflowAnalysisException(
818                         "Unpredictable stack consumption",
819                         methodGen,
820                         handle);
821             //if (DEBUG) System.out.println("Getting receiver for frame: " + frame);
822
ValueNumber instance = frame.getStackValue(numConsumed - 1);
823
824             // Is the instance locked?
825
int lockCount = lockSet.getLockCount(instance.getNumber());
826             if (lockCount > 0) {
827                 // This is a locked call site
828
obviouslyLockedSites.add(callSite);
829             }
830         }
831
832         return obviouslyLockedSites;
833     }
834 }
835
836 // vim:ts=3
837
Popular Tags