model: add "# redundant" stat
[c11tester.git] / model.cc
index 35eefa674d43d937799acd8940e00d1b0faf4cc3..df06760893018d5bc1756cd89df263c6ed117cc6 100644 (file)
--- a/model.cc
+++ b/model.cc
 #include "promise.h"
 #include "datarace.h"
 #include "threads-model.h"
+#include "output.h"
 
 #define INITIAL_THREAD_ID      0
 
 ModelChecker *model;
 
+struct bug_message {
+       bug_message(const char *str) {
+               const char *fmt = "  [BUG] %s\n";
+               msg = (char *)snapshot_malloc(strlen(fmt) + strlen(str));
+               sprintf(msg, fmt, str);
+       }
+       ~bug_message() { if (msg) snapshot_free(msg); }
+
+       char *msg;
+       void print() { model_print("%s", msg); }
+
+       SNAPSHOTALLOC
+};
+
 /**
  * Structure for holding small ModelChecker members that should be snapshotted
  */
@@ -27,6 +42,8 @@ struct model_snapshot_members {
        modelclock_t used_sequence_numbers;
        Thread *nextThread;
        ModelAction *next_backtrack;
+       std::vector< bug_message *, SnapshotAlloc<bug_message *> > bugs;
+       struct execution_stats stats;
 };
 
 /** @brief Constructor */
@@ -34,16 +51,14 @@ ModelChecker::ModelChecker(struct model_params params) :
        /* Initialize default scheduler */
        params(params),
        scheduler(new Scheduler()),
-       num_executions(0),
-       num_feasible_executions(0),
        diverge(NULL),
        earliest_diverge(NULL),
        action_trace(new action_list_t()),
        thread_map(new HashTable<int, Thread *, int>()),
-       obj_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
-       lock_waiters_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
-       condvar_waiters_map(new HashTable<const void *, action_list_t, uintptr_t, 4>()),
-       obj_thrd_map(new HashTable<void *, std::vector<action_list_t>, uintptr_t, 4 >()),
+       obj_map(new HashTable<const void *, action_list_t *, uintptr_t, 4>()),
+       lock_waiters_map(new HashTable<const void *, action_list_t *, uintptr_t, 4>()),
+       condvar_waiters_map(new HashTable<const void *, action_list_t *, uintptr_t, 4>()),
+       obj_thrd_map(new HashTable<void *, std::vector<action_list_t> *, uintptr_t, 4 >()),
        promises(new std::vector< Promise *, SnapshotAlloc<Promise *> >()),
        futurevalues(new std::vector< struct PendingFutureValue, SnapshotAlloc<struct PendingFutureValue> >()),
        pending_rel_seqs(new std::vector< struct release_seq *, SnapshotAlloc<struct release_seq *> >()),
@@ -56,7 +71,7 @@ ModelChecker::ModelChecker(struct model_params params) :
        bad_synchronization(false)
 {
        /* Allocate this "size" on the snapshotting heap */
-       priv = (struct model_snapshot_members *)calloc(1, sizeof(*priv));
+       priv = (struct model_snapshot_members *)snapshot_calloc(1, sizeof(*priv));
        /* First thread created will have id INITIAL_THREAD_ID */
        priv->next_thread_id = INITIAL_THREAD_ID;
 
@@ -88,6 +103,29 @@ ModelChecker::~ModelChecker()
        delete node_stack;
        delete scheduler;
        delete mo_graph;
+
+       for (unsigned int i = 0; i < priv->bugs.size(); i++)
+               delete priv->bugs[i];
+       priv->bugs.clear();
+       snapshot_free(priv);
+}
+
+static action_list_t * get_safe_ptr_action(HashTable<const void *, action_list_t *, uintptr_t, 4> * hash, void * ptr) {
+       action_list_t * tmp=hash->get(ptr);
+       if (tmp==NULL) {
+               tmp=new action_list_t();
+               hash->put(ptr, tmp);
+       }
+       return tmp;
+}
+
+static std::vector<action_list_t> * get_safe_ptr_vect_action(HashTable<void *, std::vector<action_list_t> *, uintptr_t, 4> * hash, void * ptr) {
+       std::vector<action_list_t> * tmp=hash->get(ptr);
+       if (tmp==NULL) {
+               tmp=new std::vector<action_list_t>();
+               hash->put(ptr, tmp);
+       }
+       return tmp;
 }
 
 /**
@@ -102,6 +140,10 @@ void ModelChecker::reset_to_initial_state()
        too_many_reads = false;
        bad_synchronization = false;
        reset_asserted();
+
+       /* Print all model-checker output before rollback */
+       fflush(model_out);
+
        snapshotObject->backTrackBeforeStep(0);
 }
 
@@ -266,55 +308,179 @@ bool ModelChecker::is_deadlocked() const
 {
        bool blocking_threads = false;
        for (unsigned int i = 0; i < get_num_threads(); i++) {
-               Thread *t = get_thread(int_to_id(i));
-               if (scheduler->is_enabled(t) != THREAD_DISABLED)
+               thread_id_t tid = int_to_id(i);
+               if (is_enabled(tid))
                        return false;
-               else if (!t->is_model_thread() && t->get_pending())
+               Thread *t = get_thread(tid);
+               if (!t->is_model_thread() && t->get_pending())
                        blocking_threads = true;
        }
        return blocking_threads;
 }
 
 /**
- * Queries the model-checker for more executions to explore and, if one
- * exists, resets the model-checker state to execute a new execution.
+ * Check if this is a complete execution. That is, have all thread completed
+ * execution (rather than exiting because sleep sets have forced a redundant
+ * execution).
  *
- * @return If there are more executions to explore, return true. Otherwise,
- * return false.
+ * @return True if the execution is complete.
  */
-bool ModelChecker::next_execution()
+bool ModelChecker::is_complete_execution() const
 {
-       DBG();
+       for (unsigned int i = 0; i < get_num_threads(); i++)
+               if (is_enabled(int_to_id(i)))
+                       return false;
+       return true;
+}
+
+/**
+ * @brief Assert a bug in the executing program.
+ *
+ * Use this function to assert any sort of bug in the user program. If the
+ * current trace is feasible (actually, a prefix of some feasible execution),
+ * then this execution will be aborted, printing the appropriate message. If
+ * the current trace is not yet feasible, the error message will be stashed and
+ * printed if the execution ever becomes feasible.
+ *
+ * @param msg Descriptive message for the bug (do not include newline char)
+ * @return True if bug is immediately-feasible
+ */
+bool ModelChecker::assert_bug(const char *msg)
+{
+       priv->bugs.push_back(new bug_message(msg));
+
+       if (isfeasibleprefix()) {
+               set_assert();
+               return true;
+       }
+       return false;
+}
+
+/**
+ * @brief Assert a bug in the executing program, asserted by a user thread
+ * @see ModelChecker::assert_bug
+ * @param msg Descriptive message for the bug (do not include newline char)
+ */
+void ModelChecker::assert_user_bug(const char *msg)
+{
+       /* If feasible bug, bail out now */
+       if (assert_bug(msg))
+               switch_to_master(NULL);
+}
+
+/** @return True, if any bugs have been reported for this execution */
+bool ModelChecker::have_bug_reports() const
+{
+       return priv->bugs.size() != 0;
+}
+
+/** @brief Print bug report listing for this execution (if any bugs exist) */
+void ModelChecker::print_bugs() const
+{
+       if (have_bug_reports()) {
+               model_print("Bug report: %zu bug%s detected\n",
+                               priv->bugs.size(),
+                               priv->bugs.size() > 1 ? "s" : "");
+               for (unsigned int i = 0; i < priv->bugs.size(); i++)
+                       priv->bugs[i]->print();
+       }
+}
 
-       num_executions++;
+/**
+ * @brief Record end-of-execution stats
+ *
+ * Must be run when exiting an execution. Records various stats.
+ * @see struct execution_stats
+ */
+void ModelChecker::record_stats()
+{
+       stats.num_total++;
+       if (!isfinalfeasible())
+               stats.num_infeasible++;
+       else if (have_bug_reports())
+               stats.num_buggy_executions++;
+       else if (is_complete_execution())
+               stats.num_complete++;
+       else
+               stats.num_redundant++;
+}
 
-       if (is_deadlocked())
-               printf("ERROR: DEADLOCK\n");
-       if (isfinalfeasible()) {
-               printf("Earliest divergence point since last feasible execution:\n");
+/** @brief Print execution stats */
+void ModelChecker::print_stats() const
+{
+       model_print("Number of complete, bug-free executions: %d\n", stats.num_complete);
+       model_print("Number of redundant executions: %d\n", stats.num_redundant);
+       model_print("Number of buggy executions: %d\n", stats.num_buggy_executions);
+       model_print("Number of infeasible executions: %d\n", stats.num_infeasible);
+       model_print("Total executions: %d\n", stats.num_total);
+       model_print("Total nodes created: %d\n", node_stack->get_total_nodes());
+}
+
+/**
+ * @brief End-of-exeuction print
+ * @param printbugs Should any existing bugs be printed?
+ */
+void ModelChecker::print_execution(bool printbugs) const
+{
+       print_program_output();
+
+       if (DBG_ENABLED() || params.verbose) {
+               model_print("Earliest divergence point since last feasible execution:\n");
                if (earliest_diverge)
                        earliest_diverge->print();
                else
-                       printf("(Not set)\n");
+                       model_print("(Not set)\n");
 
-               earliest_diverge = NULL;
-               num_feasible_executions++;
+               model_print("\n");
+               print_stats();
        }
 
-       DEBUG("Number of acquires waiting on pending release sequences: %zu\n",
-                       pending_rel_seqs->size());
+       /* Don't print invalid bugs */
+       if (printbugs)
+               print_bugs();
 
+       model_print("\n");
+       print_summary();
+}
+
+/**
+ * Queries the model-checker for more executions to explore and, if one
+ * exists, resets the model-checker state to execute a new execution.
+ *
+ * @return If there are more executions to explore, return true. Otherwise,
+ * return false.
+ */
+bool ModelChecker::next_execution()
+{
+       DBG();
+       /* Is this execution a feasible execution that's worth bug-checking? */
+       bool complete = isfinalfeasible() && (is_complete_execution() ||
+                       have_bug_reports());
+
+       /* End-of-execution bug checks */
+       if (complete) {
+               if (is_deadlocked())
+                       assert_bug("Deadlock detected");
 
-       if (isfinalfeasible() || (params.bound != 0 && priv->used_sequence_numbers > params.bound ) || DBG_ENABLED() ) {
                checkDataRaces();
-               print_summary();
        }
 
+       record_stats();
+
+       /* Output */
+       if (DBG_ENABLED() || params.verbose || have_bug_reports())
+               print_execution(complete);
+       else
+               clear_program_output();
+
+       if (complete)
+               earliest_diverge = NULL;
+
        if ((diverge = get_next_backtrack()) == NULL)
                return false;
 
        if (DBG_ENABLED()) {
-               printf("Next execution will diverge at:\n");
+               model_print("Next execution will diverge at:\n");
                diverge->print();
        }
 
@@ -329,7 +495,7 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
        case ATOMIC_WRITE:
        case ATOMIC_RMW: {
                /* linear search: from most recent to oldest */
-               action_list_t *list = obj_map->get_safe_ptr(act->get_location());
+               action_list_t *list = get_safe_ptr_action(obj_map, act->get_location());
                action_list_t::reverse_iterator rit;
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
                        ModelAction *prev = *rit;
@@ -341,7 +507,7 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
        case ATOMIC_LOCK:
        case ATOMIC_TRYLOCK: {
                /* linear search: from most recent to oldest */
-               action_list_t *list = obj_map->get_safe_ptr(act->get_location());
+               action_list_t *list = get_safe_ptr_action(obj_map, act->get_location());
                action_list_t::reverse_iterator rit;
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
                        ModelAction *prev = *rit;
@@ -352,7 +518,7 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
        }
        case ATOMIC_UNLOCK: {
                /* linear search: from most recent to oldest */
-               action_list_t *list = obj_map->get_safe_ptr(act->get_location());
+               action_list_t *list = get_safe_ptr_action(obj_map, act->get_location());
                action_list_t::reverse_iterator rit;
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
                        ModelAction *prev = *rit;
@@ -363,7 +529,7 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
        }
        case ATOMIC_WAIT: {
                /* linear search: from most recent to oldest */
-               action_list_t *list = obj_map->get_safe_ptr(act->get_location());
+               action_list_t *list = get_safe_ptr_action(obj_map, act->get_location());
                action_list_t::reverse_iterator rit;
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
                        ModelAction *prev = *rit;
@@ -378,7 +544,7 @@ ModelAction * ModelChecker::get_last_conflict(ModelAction *act)
        case ATOMIC_NOTIFY_ALL:
        case ATOMIC_NOTIFY_ONE: {
                /* linear search: from most recent to oldest */
-               action_list_t *list = obj_map->get_safe_ptr(act->get_location());
+               action_list_t *list = get_safe_ptr_action(obj_map, act->get_location());
                action_list_t::reverse_iterator rit;
                for (rit = list->rbegin(); rit != list->rend(); rit++) {
                        ModelAction *prev = *rit;
@@ -561,10 +727,8 @@ bool ModelChecker::process_mutex(ModelAction *curr) {
        }
                //otherwise fall into the lock case
        case ATOMIC_LOCK: {
-               if (curr->get_cv()->getClock(state->alloc_tid) <= state->alloc_clock) {
-                       printf("Lock access before initialization\n");
-                       set_assert();
-               }
+               if (curr->get_cv()->getClock(state->alloc_tid) <= state->alloc_clock)
+                       assert_bug("Lock access before initialization");
                state->islocked = true;
                ModelAction *unlock = get_last_unlock(curr);
                //synchronize with the previous unlock statement
@@ -578,7 +742,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) {
                //unlock the lock
                state->islocked = false;
                //wake up the other threads
-               action_list_t *waiters = lock_waiters_map->get_safe_ptr(curr->get_location());
+               action_list_t *waiters = get_safe_ptr_action(lock_waiters_map, curr->get_location());
                //activate all the waiting threads
                for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) {
                        scheduler->wake(get_thread(*rit));
@@ -590,7 +754,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) {
                //unlock the lock
                state->islocked = false;
                //wake up the other threads
-               action_list_t *waiters = lock_waiters_map->get_safe_ptr((void *) curr->get_value());
+               action_list_t *waiters = get_safe_ptr_action(lock_waiters_map, (void *) curr->get_value());
                //activate all the waiting threads
                for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) {
                        scheduler->wake(get_thread(*rit));
@@ -598,14 +762,14 @@ bool ModelChecker::process_mutex(ModelAction *curr) {
                waiters->clear();
                //check whether we should go to sleep or not...simulate spurious failures
                if (curr->get_node()->get_misc()==0) {
-                       condvar_waiters_map->get_safe_ptr(curr->get_location())->push_back(curr);
+                       get_safe_ptr_action(condvar_waiters_map, curr->get_location())->push_back(curr);
                        //disable us
                        scheduler->sleep(get_current_thread());
                }
                break;
        }
        case ATOMIC_NOTIFY_ALL: {
-               action_list_t *waiters = condvar_waiters_map->get_safe_ptr(curr->get_location());
+               action_list_t *waiters = get_safe_ptr_action(condvar_waiters_map, curr->get_location());
                //activate all the waiting threads
                for (action_list_t::iterator rit = waiters->begin(); rit != waiters->end(); rit++) {
                        scheduler->wake(get_thread(*rit));
@@ -614,7 +778,7 @@ bool ModelChecker::process_mutex(ModelAction *curr) {
                break;
        }
        case ATOMIC_NOTIFY_ONE: {
-               action_list_t *waiters = condvar_waiters_map->get_safe_ptr(curr->get_location());
+               action_list_t *waiters = get_safe_ptr_action(condvar_waiters_map, curr->get_location());
                int wakeupthread=curr->get_node()->get_misc();
                action_list_t::iterator it = waiters->begin();
                advance(it, wakeupthread);
@@ -773,8 +937,7 @@ void ModelChecker::process_relseq_fixup(ModelAction *curr, work_queue_t *work_qu
        }
 
        /* See if we have realized a data race */
-       if (checkDataRaces())
-               set_assert();
+       checkDataRaces();
 }
 
 /**
@@ -838,7 +1001,7 @@ bool ModelChecker::initialize_curr_action(ModelAction **curr)
                else if (newcurr->is_wait())
                        newcurr->get_node()->set_misc_max(2);
                else if (newcurr->is_notify_one()) {
-                       newcurr->get_node()->set_misc_max(condvar_waiters_map->get_safe_ptr(newcurr->get_location())->size());
+                       newcurr->get_node()->set_misc_max(get_safe_ptr_action(condvar_waiters_map, newcurr->get_location())->size());
                }
                return true; /* This was a new ModelAction */
        }
@@ -860,7 +1023,7 @@ bool ModelChecker::check_action_enabled(ModelAction *curr) {
                struct std::mutex_state * state = lock->get_state();
                if (state->islocked) {
                        //Stick the action in the appropriate waiting queue
-                       lock_waiters_map->get_safe_ptr(curr->get_location())->push_back(curr);
+                       get_safe_ptr_action(lock_waiters_map, curr->get_location())->push_back(curr);
                        return false;
                }
        } else if (curr->get_type() == THREAD_JOIN) {
@@ -923,7 +1086,7 @@ Thread * ModelChecker::check_current_action(ModelAction *curr)
 
        /* Initialize work_queue with the "current action" work */
        work_queue_t work_queue(1, CheckCurrWorkEntry(curr));
-       while (!work_queue.empty()) {
+       while (!work_queue.empty() && !has_asserted()) {
                WorkQueueEntry work = work_queue.front();
                work_queue.pop_front();
 
@@ -1004,7 +1167,8 @@ void ModelChecker::check_curr_backtracking(ModelAction * curr) {
        }
 }
 
-bool ModelChecker::promises_expired() {
+bool ModelChecker::promises_expired() const
+{
        for (unsigned int promise_index = 0; promise_index < promises->size(); promise_index++) {
                Promise *promise = (*promises)[promise_index];
                if (promise->get_expiration()<priv->used_sequence_numbers) {
@@ -1016,12 +1180,14 @@ bool ModelChecker::promises_expired() {
 
 /** @return whether the current partial trace must be a prefix of a
  * feasible trace. */
-bool ModelChecker::isfeasibleprefix() {
+bool ModelChecker::isfeasibleprefix() const
+{
        return promises->size() == 0 && pending_rel_seqs->size() == 0 && isfeasible();
 }
 
 /** @return whether the current partial trace is feasible. */
-bool ModelChecker::isfeasible() {
+bool ModelChecker::isfeasible() const
+{
        if (DBG_ENABLED() && mo_graph->checkForRMWViolation())
                DEBUG("Infeasible: RMW violation\n");
 
@@ -1030,7 +1196,8 @@ bool ModelChecker::isfeasible() {
 
 /** @return whether the current partial trace is feasible other than
  * multiple RMW reading from the same store. */
-bool ModelChecker::isfeasibleotherthanRMW() {
+bool ModelChecker::isfeasibleotherthanRMW() const
+{
        if (DBG_ENABLED()) {
                if (mo_graph->checkForCycles())
                        DEBUG("Infeasible: modification order cycles\n");
@@ -1047,7 +1214,8 @@ bool ModelChecker::isfeasibleotherthanRMW() {
 }
 
 /** Returns whether the current completed trace is feasible. */
-bool ModelChecker::isfinalfeasible() {
+bool ModelChecker::isfinalfeasible() const
+{
        if (DBG_ENABLED() && promises->size() != 0)
                DEBUG("Infeasible: unrevolved promises\n");
 
@@ -1085,7 +1253,7 @@ void ModelChecker::check_recency(ModelAction *curr, const ModelAction *rf) {
                //accidentally clear by rolling back
                if (!isfeasible())
                        return;
-               std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
+               std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location());
                int tid = id_to_int(curr->get_tid());
 
                /* Skip checks */
@@ -1177,7 +1345,7 @@ void ModelChecker::check_recency(ModelAction *curr, const ModelAction *rf) {
  */
 bool ModelChecker::r_modification_order(ModelAction *curr, const ModelAction *rf)
 {
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location());
        unsigned int i;
        bool added = false;
        ASSERT(curr->is_read());
@@ -1236,7 +1404,7 @@ bool ModelChecker::r_modification_order(ModelAction *curr, const ModelAction *rf
  */
 void ModelChecker::post_r_modification_order(ModelAction *curr, const ModelAction *rf)
 {
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location());
        unsigned int i;
        ASSERT(curr->is_read());
 
@@ -1307,7 +1475,7 @@ void ModelChecker::post_r_modification_order(ModelAction *curr, const ModelActio
  */
 bool ModelChecker::w_modification_order(ModelAction *curr)
 {
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location());
        unsigned int i;
        bool added = false;
        ASSERT(curr->is_write());
@@ -1429,7 +1597,7 @@ bool ModelChecker::thin_air_constraint_may_allow(const ModelAction * writer, con
  */
 bool ModelChecker::mo_may_allow(const ModelAction *writer, const ModelAction *reader)
 {
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(reader->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, reader->get_location());
        unsigned int i;
        /* Iterate over all threads */
        for (i = 0; i < thrd_lists->size(); i++) {
@@ -1521,7 +1689,7 @@ bool ModelChecker::release_seq_heads(const ModelAction *rf,
        /* else relaxed write; check modification order for contiguous subsequence
         * -> rf must be same thread as release */
        int tid = id_to_int(rf->get_tid());
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(rf->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, rf->get_location());
        action_list_t *list = &(*thrd_lists)[tid];
        action_list_t::const_reverse_iterator rit;
 
@@ -1556,7 +1724,7 @@ bool ModelChecker::release_seq_heads(const ModelAction *rf,
                ModelAction *last = get_last_action(int_to_id(i));
                Thread *th = get_thread(int_to_id(i));
                if ((last && rf->happens_before(last)) ||
-                               !scheduler->is_enabled(th) ||
+                               !is_enabled(th) ||
                                th->is_complete())
                        future_ordered = true;
 
@@ -1700,9 +1868,7 @@ bool ModelChecker::resolve_release_sequences(void *location, work_queue_t *work_
        }
 
        // If we resolved promises or data races, see if we have realized a data race.
-       if (checkDataRaces()) {
-               set_assert();
-       }
+       checkDataRaces();
 
        return updated;
 }
@@ -1719,9 +1885,9 @@ void ModelChecker::add_action_to_lists(ModelAction *act)
        int tid = id_to_int(act->get_tid());
        action_trace->push_back(act);
 
-       obj_map->get_safe_ptr(act->get_location())->push_back(act);
+       get_safe_ptr_action(obj_map, act->get_location())->push_back(act);
 
-       std::vector<action_list_t> *vec = obj_thrd_map->get_safe_ptr(act->get_location());
+       std::vector<action_list_t> *vec = get_safe_ptr_vect_action(obj_thrd_map, act->get_location());
        if (tid >= (int)vec->size())
                vec->resize(priv->next_thread_id);
        (*vec)[tid].push_back(act);
@@ -1732,9 +1898,9 @@ void ModelChecker::add_action_to_lists(ModelAction *act)
 
        if (act->is_wait()) {
                void *mutex_loc=(void *) act->get_value();
-               obj_map->get_safe_ptr(mutex_loc)->push_back(act);
+               get_safe_ptr_action(obj_map, mutex_loc)->push_back(act);
 
-               std::vector<action_list_t> *vec = obj_thrd_map->get_safe_ptr(mutex_loc);
+               std::vector<action_list_t> *vec = get_safe_ptr_vect_action(obj_thrd_map, mutex_loc);
                if (tid >= (int)vec->size())
                        vec->resize(priv->next_thread_id);
                (*vec)[tid].push_back(act);
@@ -1770,7 +1936,7 @@ ModelAction * ModelChecker::get_last_action(thread_id_t tid) const
 ModelAction * ModelChecker::get_last_seq_cst(ModelAction *curr) const
 {
        void *location = curr->get_location();
-       action_list_t *list = obj_map->get_safe_ptr(location);
+       action_list_t *list = get_safe_ptr_action(obj_map, location);
        /* Find: max({i in dom(S) | seq_cst(t_i) && isWrite(t_i) && samevar(t_i, t)}) */
        action_list_t::reverse_iterator rit;
        for (rit = list->rbegin(); rit != list->rend(); rit++)
@@ -1790,7 +1956,7 @@ ModelAction * ModelChecker::get_last_seq_cst(ModelAction *curr) const
 ModelAction * ModelChecker::get_last_unlock(ModelAction *curr) const
 {
        void *location = curr->get_location();
-       action_list_t *list = obj_map->get_safe_ptr(location);
+       action_list_t *list = get_safe_ptr_action(obj_map, location);
        /* Find: max({i in dom(S) | isUnlock(t_i) && samevar(t_i, t)}) */
        action_list_t::reverse_iterator rit;
        for (rit = list->rbegin(); rit != list->rend(); rit++)
@@ -2012,7 +2178,7 @@ void ModelChecker::compute_relseq_breakwrites(ModelAction *curr)
  */
 void ModelChecker::build_reads_from_past(ModelAction *curr)
 {
-       std::vector<action_list_t> *thrd_lists = obj_thrd_map->get_safe_ptr(curr->get_location());
+       std::vector<action_list_t> *thrd_lists = get_safe_ptr_vect_action(obj_thrd_map, curr->get_location());
        unsigned int i;
        ASSERT(curr->is_read());
 
@@ -2061,18 +2227,15 @@ void ModelChecker::build_reads_from_past(ModelAction *curr)
                }
        }
 
-       if (!initialized) {
-               /** @todo Need a more informative way of reporting errors. */
-               printf("ERROR: may read from uninitialized atomic\n");
-               set_assert();
-       }
+       if (!initialized)
+               assert_bug("May read from uninitialized atomic");
 
        if (DBG_ENABLED() || !initialized) {
-               printf("Reached read action:\n");
+               model_print("Reached read action:\n");
                curr->print();
-               printf("Printing may_read_from\n");
+               model_print("Printing may_read_from\n");
                curr->get_node()->print_may_read_from();
-               printf("End printing may_read_from\n");
+               model_print("End printing may_read_from\n");
        }
 }
 
@@ -2092,20 +2255,22 @@ bool ModelChecker::sleep_can_read_from(ModelAction * curr, const ModelAction *wr
        }
 }
 
-static void print_list(action_list_t *list)
+static void print_list(action_list_t *list, int exec_num = -1)
 {
        action_list_t::iterator it;
 
-       printf("---------------------------------------------------------------------\n");
-       printf("Trace:\n");
+       model_print("---------------------------------------------------------------------\n");
+       if (exec_num >= 0)
+               model_print("Execution %d:\n", exec_num);
+
        unsigned int hash=0;
 
        for (it = list->begin(); it != list->end(); it++) {
                (*it)->print();
                hash=hash^(hash<<3)^((*it)->hash());
        }
-       printf("HASH %u\n", hash);
-       printf("---------------------------------------------------------------------\n");
+       model_print("HASH %u\n", hash);
+       model_print("---------------------------------------------------------------------\n");
 }
 
 #if SUPPORT_MOD_ORDER_DUMP
@@ -2136,26 +2301,22 @@ void ModelChecker::dumpGraph(char *filename) {
 }
 #endif
 
-void ModelChecker::print_summary()
+/** @brief Prints an execution trace summary. */
+void ModelChecker::print_summary() const
 {
-       printf("\n");
-       printf("Number of executions: %d\n", num_executions);
-       printf("Number of feasible executions: %d\n", num_feasible_executions);
-       printf("Total nodes created: %d\n", node_stack->get_total_nodes());
-
 #if SUPPORT_MOD_ORDER_DUMP
        scheduler->print();
        char buffername[100];
-       sprintf(buffername, "exec%04u", num_executions);
+       sprintf(buffername, "exec%04u", stats.num_total);
        mo_graph->dumpGraphToFile(buffername);
-       sprintf(buffername, "graph%04u", num_executions);
+       sprintf(buffername, "graph%04u", stats.num_total);
        dumpGraph(buffername);
 #endif
 
        if (!isfinalfeasible())
-               printf("INFEASIBLE EXECUTION!\n");
-       print_list(action_trace);
-       printf("\n");
+               model_print("INFEASIBLE EXECUTION!\n");
+       print_list(action_trace, stats.num_total);
+       model_print("\n");
 }
 
 /**
@@ -2265,6 +2426,10 @@ bool ModelChecker::take_step() {
        /* Infeasible -> don't take any more steps */
        if (!isfeasible())
                return false;
+       else if (isfeasibleprefix() && have_bug_reports()) {
+               set_assert();
+               return false;
+       }
 
        if (params.bound != 0) {
                if (priv->used_sequence_numbers > params.bound) {
@@ -2286,7 +2451,7 @@ bool ModelChecker::take_step() {
         */
        if (!pending_rel_seqs->empty() && (!next || next->is_model_thread()) &&
                        isfinalfeasible() && !unrealizedraces.empty()) {
-               printf("*** WARNING: release sequence fixup action (%zu pending release seuqences) ***\n",
+               model_print("*** WARNING: release sequence fixup action (%zu pending release seuqences) ***\n",
                                pending_rel_seqs->size());
                ModelAction *fixup = new ModelAction(MODEL_FIXUP_RELSEQ,
                                std::memory_order_seq_cst, NULL, VALUE_NONE,