//
// Copyright (C) 2007 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.listener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.search.DFSearch;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.search.heuristic.BFSHeuristic;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* A listener that tracks information about the stack depth of when a lock is first acquired. If
*
* Writing a test for this class is very difficult. Hence, a lot of asserts are added.
*/
public class LockedStackDepth extends ListenerAdapter
{
private static final Logger s_logger = JPF.getLogger(LockedStackDepth.class.getName());
private static final Integer EMPTY[] = new Integer[0];
private static final int THREAD_FLAG = 0x80000000;
private final HashMap<Integer, Operation> m_operations = new HashMap<Integer, Operation>();
private final HashMap<Integer, Integer> m_state = new HashMap<Integer, Integer>();
private final HashMap<Operation, Integer> m_index = new HashMap<Operation, Integer>();
private final ArrayList<Operation> m_apply = new ArrayList<Operation>();
private Operation m_current;
public int getLockedStackDepth(ElementInfo lock)
{
Integer result;
int lockIndex;
lockIndex = lock.getObjectRef();
result = m_state.get(makeKey(lock));
if (s_logger.isLoggable(Level.INFO))
s_logger.info("Depth = " + result + " | Lock Index = " + lockIndex + " | Lock = " + lock);
if (result == null)
return(-1);
assert result >= 0;
return(result);
}
public List<ElementInfo> getLockedInTopFrame(ThreadInfo thread)
{
ArrayList<ElementInfo> result;
ElementInfo lock;
int threadDepth;
threadDepth = thread.getStackDepth();
result = new ArrayList<ElementInfo>();
for (Integer key : m_state.keySet())
{
if (key < 0)
continue;
if (threadDepth != m_state.get(key))
continue;
lock = thread.getElementInfo(key);
if (lock == null)
continue;
if (!lock.isLockedBy(thread))
continue;
result.add(lock);
}
return(result);
}
@Override
public void objectLocked(VM vm, ThreadInfo thread, ElementInfo ei)
{
ElementInfo lock;
Integer depth;
lock = ei;
logStack(thread);
depth = new Operation(thread, null).getOldDepth();
if (depth == null)
depth = thread.getStackDepth();
assert thread.getLockCount() == 0;
assert thread.getLockObject() == null;
assert lock.isLockedBy(thread);
if (m_state.containsKey(makeKey(lock))) // So that a breakpoint on the next line will only get hit if the assert will trigger.
assert !m_state.containsKey(makeKey(lock));
assert !m_state.containsKey(makeKey(thread));
assert depth >= 0;
new Operation(lock, depth);
}
@Override
public void objectUnlocked(VM vm, ThreadInfo thread, ElementInfo ei)
{
ElementInfo lock;
Integer depth;
logStack(thread);
lock = ei;
depth = new Operation(lock, null).getOldDepth();
assert !m_state.containsKey(makeKey(lock));
assert !m_state.containsKey(makeKey(thread));
assert depth >= 0;
if (thread.isWaiting())
{
assert !lock.isLockedBy(thread);
assert lock.getLockCount() == 0;
assert thread.getLockCount() > 0;
assert thread.getLockObject() == lock;
new Operation(thread, depth);
}
else
{
assert lock.isLockedBy(thread);
assert lock.getLockCount() > 0;
assert thread.getLockCount() == 0;
assert thread.getLockObject() == null;
}
}
@Override
public void searchStarted(Search search)
{
m_operations.clear();
m_state.clear();
m_current = null;
}
@Override
public void stateAdvanced(Search search)
{
Integer id;
id = search.getStateId();
if (!m_operations.containsKey(id)) // Don't overwrite the original chain of Operations to get to the same state. The original chain is more likely to be shorter.
m_operations.put(id, m_current);
if (s_logger.isLoggable(Level.FINE))
s_logger.fine("State Advanced: " + id);
logState();
}
@Override
public void stateProcessed(Search search)
{
Integer id;
if (!(search instanceof DFSearch)) // Can't remove from m_operations since Search could go back to the state.
if (!(search instanceof BFSHeuristic))
return;
id = search.getStateId();
m_operations.remove(id); // DFSearch won't ever revisit this state. It is safe to remove and allow for cleanup.
if (s_logger.isLoggable(Level.FINE))
s_logger.fine("State Processed: " + id);
}
@Override
public void stateBacktracked(Search search)
{
switchTo(search);
}
@Override
public void stateRestored(Search search)
{
switchTo(search);
}
private void switchTo(Search search)
{
Operation next;
Integer id;
id = search.getStateId();
next = m_operations.get(id);
if (s_logger.isLoggable(Level.FINE))
s_logger.fine("State Switching: " + id);
assert (id <= 0) || (m_operations.containsKey(id));
switchTo(next);
m_current = next;
logState();
if (s_logger.isLoggable(Level.FINE))
s_logger.fine("State Switched: " + id);
}
private void switchTo(Operation next)
{
Operation operation;
Integer index;
int i;
for (operation = next; operation != null; operation = operation.getParent()) // Go through all of the operations leading back to the root.
{
m_index.put(operation, m_apply.size()); // Keep track of the index into m_apply where operation is found
m_apply.add(operation);
}
index = null;
for (operation = m_current; operation != null; operation = operation.getParent()) // Go through all of the operations leading back to the root.
{
index = m_index.get(operation);
if (index != null) // If a common ancestor is found, stop going back.
break;
operation.revert(); // Revert the operation since it isn't common to both states.
}
if (index == null)
index = m_apply.size(); // No common ancestor found. Must apply all of the operations.
for (i = index; --i >= 0; ) // Apply all of the operations required to get back to the "next" state.
m_apply.get(i).apply();
m_index.clear();
m_apply.clear();
}
private void logState()
{
StringBuilder message;
String type;
Integer key, keys[], depth;
int i;
if (!s_logger.isLoggable(Level.FINER))
return;
message = new StringBuilder();
keys = m_state.keySet().toArray(EMPTY);
Arrays.sort(keys);
message.append("State | Size = ");
message.append(keys.length);
for (i = 0; i < keys.length; i++)
{
key = keys[i];
depth = m_state.get(key);
if ((key & THREAD_FLAG) != 0)
type = "Thread";
else
type = "Lock";
message.append('\n');
message.append("Depth = ");
message.append(depth);
message.append(" | Key = ");
message.append(key & ~THREAD_FLAG);
message.append(" | ");
message.append(type);
}
s_logger.finer(message.toString());
}
private void logStack(ThreadInfo thread)
{
if (!s_logger.isLoggable(Level.FINEST))
return;
s_logger.finest(thread.getStackTrace());
}
private static int makeKey(ElementInfo lock)
{
return(lock.getObjectRef());
}
private static int makeKey(ThreadInfo thread)
{
return(thread.getThreadObjectRef() ^ THREAD_FLAG);
}
private class Operation
{
private final Operation m_parent;
private final Integer m_key;
private final Integer m_oldDepth;
private final Integer m_newDepth;
public Operation(ElementInfo lock, Integer newDepth)
{
this(makeKey(lock), newDepth);
}
public Operation(ThreadInfo thread, Integer newDepth)
{
this(makeKey(thread), newDepth);
}
private Operation(Integer key, Integer newDepth)
{
m_parent = m_current;
m_current = this;
m_key = key;
m_newDepth = newDepth;
m_oldDepth = m_state.get(key);
apply();
}
public Operation getParent()
{
return(m_parent);
}
public Integer getOldDepth()
{
return(m_oldDepth);
}
@SuppressWarnings("unused")
public Integer getNewDepth()
{
return(m_newDepth);
}
public void apply()
{
change(m_newDepth);
log("Apply ");
}
public void revert()
{
change(m_oldDepth);
log("Revert");
}
private void change(Integer depth)
{
Integer previous;
if (depth == null)
m_state.remove(m_key);
else
{
previous = m_state.put(m_key, depth);
assert previous == null;
}
}
private void log(String header)
{
String message, subheader, depthStr, type;
Integer depth;
if (!s_logger.isLoggable(Level.FINE))
return;
if (m_newDepth != null)
{
subheader = "Add ";
depth = m_newDepth;
}
else
{
subheader = "Remove";
depth = m_oldDepth;
}
depthStr = String.valueOf(depth);
switch (depthStr.length())
{
case 1: depthStr = " " + depthStr; break;
case 2: depthStr = " " + depthStr; break;
case 3: depthStr = " " + depthStr; break;
default: break;
}
if ((m_key & THREAD_FLAG) != 0)
type = "Thread";
else
type = "Lock";
message = header + " " + subheader + " | Depth = " + depthStr + " | Key = " + (m_key & ~THREAD_FLAG) + " | " + type;
s_logger.fine(message);
}
}
}