// // Copyright (C) 2008 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.vm; import java.util.logging.Logger; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; /** * a FieldLockInfo implementation with the following strategy: * * - at each check, store the intersection of the current threads lock set * with the previous field lock set * - if the access was checked less than CHECK_THRESHOLD times, report the * field as unprotected * - if the field lock set doesn't become empty after CHECK_THRESHOLD, report * the field as protected * - as an optimization, raise the check level above the threshold if we * have a good probability that a current lock is a protection lock for this * field * - continue to check even after reaching the threshold, so that we * can at least report a violated assumption * * NOTE there is a subtle problem: if we ever falsely assume lock protection * in a path that subsequently recycles the shared object (e.g. by leading * into an end state), we loose the assumption. If this is followed by * a backtrack and execution of a path that uses a conflicting assumption * (different or no lock), we will NOT detect potential races unless * vm.por.sync_detection.pindown is set (which has some runtime costs) */ public class StatisticFieldLockInfoFactory implements FieldLockInfoFactory { static Logger log = JPF.getLogger("gov.nasa.jpf.vm.FieldLockInfo"); /** * the number of checks after which we decide if a non-empty lock set * means this field is protected */ static int CHECK_THRESHOLD = 5; /** * do we want objects with final field lock assumptions to be pinned * down (not garbage collected), to make sure we don't loose these * assumptions and subsequently fail to detect an assumption violation * after backtracking (see above) */ static boolean PINDOWN = false; /** * do we look for strong locking candidates (i.e. assume protection * if there is a lock related to the object). * NOTE this can lead to undetected race conditions if the assumption * subsequently fails */ static boolean AGRESSIVE = false; public StatisticFieldLockInfoFactory (Config conf) { CHECK_THRESHOLD = conf.getInt("vm.por.sync_detection.threshold", CHECK_THRESHOLD); PINDOWN = conf.getBoolean("vm.por.sync_detection.pindown", PINDOWN); AGRESSIVE = conf.getBoolean("vm.por.sync_detection.agressive",AGRESSIVE); } public FieldLockInfo createFieldLockInfo (ThreadInfo ti, ElementInfo ei, FieldInfo fi) { int[] currentLockRefs = ti.getLockedObjectReferences(); int nLocks = currentLockRefs.length; if (nLocks == 0) { return FieldLockInfo.empty; // not protected, never will } else { if (AGRESSIVE) { int lockCandidateRef = strongProtectionCandidate(ei,fi,currentLockRefs); if (lockCandidateRef != MJIEnv.NULL) { // NOTE we raise the checklevel return new SingleLockFli( ti, lockCandidateRef, CHECK_THRESHOLD); } } if (nLocks == 1) { // most common case return new SingleLockFli( ti, currentLockRefs[0], 0); } else { return new MultiLockFli( ti, fi, currentLockRefs); } } } /** * check if the current thread lockset contains a lock with a high probability * that it is a protection lock for this field. We need this to avoid * state explosion due to the number of fields to check. Note that we don't * necessarily have to answer/decide which one is the best match in case of * several candidates (if we don't use this to reduce to StatisticFieldLockInfo1) * * For instance fields, this would be a lock with a distance <= 1. * For static fields, the corresponding class object is a good candidate. */ int strongProtectionCandidate (ElementInfo ei, FieldInfo fi, int[] currentLockRefs) { int n = currentLockRefs.length; Heap heap = VM.getVM().getHeap(); if (fi.isStatic()) { // static field, check for class object locking ClassInfo ci = fi.getClassInfo(); int cref = ci.getClassObjectRef(); for (int i=0; i<n; i++) { if (currentLockRefs[i] == cref) { ElementInfo e = heap.get(cref); log.info("sync-detection: " + ei + " assumed to be synced on class object: @" + e); return cref; } } } else { // instance field, use lock distance as a heuristic int objRef = ei.getObjectRef(); for (int i=0; i<n; i++) { int eidx = currentLockRefs[i]; // case 1: synchronization on field owner itself if (eidx == objRef) { log.info("sync-detection: " + ei + " assumed to be synced on itself"); return objRef; } ElementInfo e = heap.get(eidx); // case 2: synchronization on sibling field that is a private lock object if (ei.hasRefField(eidx)) { log.info("sync-detection: "+ ei + " assumed to be synced on sibling: " + e); return eidx; } // case 3: synchronization on owner of object holding field (sync wrapper) if (e.hasRefField(objRef)) { log.info("sync-detection: " + ei + " assumed to be synced on object wrapper: " + e); return eidx; } } } return -1; } //--- root for our concrete FieldLockInfo classes static abstract class StatisticFieldLockInfo extends FieldLockInfo { int checkLevel; public boolean isProtected () { return (checkLevel >= CHECK_THRESHOLD); } public boolean needsPindown (ElementInfo ei) { return PINDOWN && (checkLevel >= CHECK_THRESHOLD); } protected void checkFailedLockAssumption(ThreadInfo ti, ElementInfo ei, FieldInfo fi) { if (checkLevel >= CHECK_THRESHOLD) { lockAssumptionFailed(ti,ei,fi); } } } //--- Fli for a single lock static class SingleLockFli extends StatisticFieldLockInfo { int lockRef; SingleLockFli (ThreadInfo ti, int lockRef, int nChecks) { tiLastCheck = ti; this.lockRef = lockRef; checkLevel = nChecks; } protected int[] getCandidateLockSet() { int[] set = { lockRef }; return set; } public FieldLockInfo checkProtection (ThreadInfo ti, ElementInfo ei, FieldInfo fi) { int[] currentLockRefs = ti.getLockedObjectReferences(); int nLocks = currentLockRefs.length; checkLevel++; for (int i=0; i<nLocks; i++) { if (currentLockRefs[i] == lockRef) { return this; } } checkFailedLockAssumption(ti, ei, fi); return empty; } /** * only called at the end of the gc on all live objects. The recycled ones * are either already nulled in the heap, or are not marked as live */ public FieldLockInfo cleanUp (Heap heap) { ElementInfo ei = heap.get(lockRef); if (!heap.isAlive(ei)) { return FieldLockInfo.empty; } else { return this; } } public String toString() { return ("SingleLockFli {checkLevel="+checkLevel+",lock="+lockRef + '}'); } } //--- StatisticFieldLockInfo for lock sets static class MultiLockFli extends StatisticFieldLockInfo { int[] lockRefSet; // this is only used once during prototype generation public MultiLockFli (ThreadInfo ti, FieldInfo fi, int[] currentLockRefs) { lockRefSet = currentLockRefs; } protected int[] getCandidateLockSet() { return lockRefSet; } public FieldLockInfo checkProtection (ThreadInfo ti, ElementInfo ei, FieldInfo fi) { int[] currentLockRefs = ti.getLockedObjectReferences(); int nLocks = currentLockRefs.length; checkLevel++; if (nLocks == 0) { // no current locks, so intersection is empty checkFailedLockAssumption(ti, ei, fi); return empty; } else { // we had a lock set, and there currently is at least one lock held int l =0; int[] newLset = new int[lockRefSet.length]; for (int i=0; i<nLocks; i++) { // get the set intersection int leidx = currentLockRefs[i]; for (int j=0; j<lockRefSet.length; j++) { if (lockRefSet[j] == leidx) { newLset[l++] = leidx; break; // sets don't contain duplicates } } } if (l == 0) { // intersection empty checkFailedLockAssumption(ti, ei, fi); return empty; } else if (l == 1) { // only one candidate left return new SingleLockFli( ti, newLset[0], checkLevel); } else if (l < newLset.length) { // candidate set did shrink lockRefSet = new int[l]; System.arraycopy(newLset, 0, lockRefSet, 0, l); } else { // no change } } tiLastCheck = ti; return this; } /** * only called at the end of the gc on all live objects. The recycled ones * are either already nulled in the heap, or are not marked as live */ public FieldLockInfo cleanUp (Heap heap) { int[] newSet = null; int l = 0; if (lockRefSet != null) { for (int i=0; i<lockRefSet.length; i++) { ElementInfo ei = heap.get(lockRefSet[i]); if (!heap.isAlive(ei)) { // we got a stale one, so we have to change us if (newSet == null) { // first one, copy everything up to it newSet = new int[lockRefSet.length-1]; if (i > 0) { System.arraycopy(lockRefSet, 0, newSet, 0, i); l = i; } } } else { if (newSet != null) { // we already had a dangling ref, now copy the live ones newSet[l++] = lockRefSet[i]; } } } } if (l == 1) { assert (newSet != null); return new SingleLockFli(tiLastCheck, newSet[0], checkLevel); } else { if (newSet != null) { if (l == newSet.length) { // we just had one stale ref lockRefSet = newSet; } else { // several stales - make a new copy if (l == 0) { return empty; } else { lockRefSet = new int[l]; System.arraycopy(newSet, 0, lockRefSet, 0, l); } } } return this; } } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("MultiLockFli {"); sb.append("checkLevel="); sb.append(checkLevel); sb.append(",lset=["); if (lockRefSet != null) { for (int i=0; i<lockRefSet.length; i++) { if (i>0) { sb.append(','); } sb.append(lockRefSet[i]); } } sb.append("]}"); return sb.toString(); } } }