Adds a new CHECK macro that abort()s on failure. (#1532)
authorTim King <taking@cs.nyu.edu>
Wed, 7 Feb 2018 18:19:04 +0000 (10:19 -0800)
committerAina Niemetz <aina.niemetz@gmail.com>
Wed, 7 Feb 2018 18:19:04 +0000 (10:19 -0800)
src/base/Makefile.am
src/base/cvc4_check.cpp [new file with mode: 0644]
src/base/cvc4_check.h [new file with mode: 0644]
src/theory/bv/aig_bitblaster.cpp
src/util/statistics_registry.cpp
test/unit/Makefile.am
test/unit/util/check_white.h [new file with mode: 0644]

index 5537bbbdd15781555e44b0a8d1704a9b179fb60e..7dd6f47e5c5c6a0d03850166b66a85f86066cdfc 100644 (file)
@@ -20,6 +20,8 @@ libbase_la_SOURCES = \
        configuration_private.h \
        cvc4_assert.cpp \
        cvc4_assert.h \
+       cvc4_check.cpp \
+       cvc4_check.h \
        exception.cpp \
        exception.h \
        listener.cpp \
diff --git a/src/base/cvc4_check.cpp b/src/base/cvc4_check.cpp
new file mode 100644 (file)
index 0000000..5976ac3
--- /dev/null
@@ -0,0 +1,44 @@
+/*********************                                                        */
+/*! \file cvc4_check.cpp
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Tim King
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2018 by the authors listed in the file AUTHORS
+ ** in the top-level source directory) and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief Assertion utility classes, functions and macros.
+ **
+ ** Implementation of assertion utility classes, functions and macros.
+ **/
+
+#include "base/cvc4_check.h"
+
+#include <cstdlib>
+#include <iostream>
+
+namespace CVC4 {
+
+FatalStream::FatalStream(const char* function, const char* file, int line)
+{
+  stream() << "Fatal failure within " << function << " at " << file << ":"
+           << line << "\n";
+}
+
+FatalStream::~FatalStream()
+{
+  Flush();
+  abort();
+}
+
+std::ostream& FatalStream::stream() { return std::cerr; }
+
+void FatalStream::Flush()
+{
+  stream() << std::endl;
+  stream().flush();
+}
+
+}  // namespace CVC4
diff --git a/src/base/cvc4_check.h b/src/base/cvc4_check.h
new file mode 100644 (file)
index 0000000..fb4ec0b
--- /dev/null
@@ -0,0 +1,144 @@
+/*********************                                                        */
+/*! \file cvc4_check.h
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Tim King
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2018 by the authors listed in the file AUTHORS
+ ** in the top-level source directory) and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief Assertion utility classes, functions and macros.
+ **
+ ** The CHECK utility classes, functions and macros are related to the Assert()
+ ** macros defined in base/cvc4_assert.h. The major distinguishing attribute
+ ** is the CHECK's abort() the process on failures while Assert() statements
+ ** throw C++ exceptions.
+ **
+ ** The main usage in the file is the CHECK macros. The CHECK macros assert a
+ ** condition and aborts()'s the process if the condition is not satisfied. The
+ ** macro leaves a hanging ostream for the user to specify additional
+ ** information about the failure. Example usage:
+ **   CHECK(x >= 0) << "x must be positive.";
+ **
+ ** DCHECK is a CHECK that is only enabled in debug builds.
+ **   DCHECK(pointer != nullptr);
+ **
+ ** CVC4_FATAL() can be used to indicate unreachable code.
+ **
+ ** The CHECK and DCHECK macros are not safe for use in signal-handling code.
+ ** TODO(taking): Add a signal-handling safe version of CHECK.
+ **/
+
+#include "cvc4_private.h"
+
+#ifndef __CVC4__CHECK_H
+#define __CVC4__CHECK_H
+
+#include <ostream>
+
+// Define CVC4_NO_RETURN macro replacement for [[noreturn]].
+#if defined(SWIG)
+#define CVC4_NO_RETURN
+// SWIG does not yet support [[noreturn]] so emit nothing instead.
+#else
+#define CVC4_NO_RETURN [[noreturn]]
+// Not checking for whether the compiler supports [[noreturn]] using
+// __has_cpp_attribute as GCC 4.8 is too widespread and does not support this.
+// We instead assume this is C++11 (or later) and [[noreturn]] is available.
+#endif  // defined(SWIG)
+
+// Define CVC4_PREDICT_FALSE(x) that helps the compiler predict that x will be
+// false (if there is compiler support).
+#ifdef __has_builtin
+#if __has_builtin(__builtin_expect)
+#define CVC4_PREDICT_FALSE(x) (__builtin_expect(x, false))
+#else
+#define CVC4_PREDICT_FALSE(x) x
+#endif
+#else
+#define CVC4_PREDICT_FALSE(x) x
+#endif
+
+namespace CVC4 {
+
+// Implementation notes:
+// To understand FatalStream and OStreamVoider, it is useful to understand
+// how a CHECK is structured. CHECK(cond) is roughly the following pattern:
+//  cond ? (void)0 : OstreamVoider() & FatalStream().stream()
+// This is a carefully crafted message to achieve a hanging ostream using
+// operator precedence. The line `CHECK(cond) << foo << bar;` will bind as
+// follows:
+//  `cond ? ((void)0) : (OSV() & ((FS().stream() << foo) << bar));`
+// Once the expression is evaluated, the destructor ~FatalStream() of the
+// temporary object is then run, which abort()'s the process. The role of the
+// OStreamVoider() is to match the void type of the true branch.
+
+// Class that provides an ostream and whose destructor aborts! Direct usage of
+// this class is discouraged.
+class FatalStream
+{
+ public:
+  FatalStream(const char* function, const char* file, int line);
+  CVC4_NO_RETURN ~FatalStream();
+
+  std::ostream& stream();
+
+ private:
+  void Flush();
+};
+
+// Helper class that changes the type of an std::ostream& into a void. See
+// "Implementation notes" for more information.
+class OstreamVoider
+{
+ public:
+  OstreamVoider() {}
+  // The operator precedence between operator& and operator<< is critical here.
+  void operator&(std::ostream&) {}
+};
+
+// CVC4_FATAL() always aborts a function and provides a convenient way of
+// formatting error messages. This can be used instead of a return type.
+//
+// Example function that returns a type Foo:
+//   Foo bar(T t) {
+//     switch(t.type()) {
+//     ...
+//     default:
+//       CVC4_FATAL() << "Unknown T type " << t.enum();
+//     }
+//   }
+#define CVC4_FATAL() \
+  FatalStream(__PRETTY_FUNCTION__, __FILE__, __LINE__).stream()
+
+// If `cond` is true, log an error message and abort the process.
+// Otherwise, does nothing. This leaves a hanging std::ostream& that can be
+// inserted into.
+#define CVC4_FATAL_IF(cond, function, file, line) \
+  CVC4_PREDICT_FALSE(!(cond))                     \
+  ? (void)0 : OstreamVoider() & FatalStream(function, file, line).stream()
+
+// If `cond` is false, log an error message and abort()'s the process.
+// Otherwise, does nothing. This leaves a hanging std::ostream& that can be
+// inserted into using operator<<. Example usages:
+//   CHECK(x >= 0);
+//   CHECK(x >= 0) << "x must be positive";
+//   CHECK(x >= 0) << "expected a positive value. Got " << x << " instead";
+#define CHECK(cond)                                               \
+  CVC4_FATAL_IF(!(cond), __PRETTY_FUNCTION__, __FILE__, __LINE__) \
+      << "Check failure\n\n " << #cond << "\n"
+
+// DCHECK is a variant of CHECK() that is only checked when CVC4_ASSERTIONS is
+// defined. We rely on the optimizer to remove the deadcode.
+#ifdef CVC4_ASSERTIONS
+#define DCHECK(cond) CHECK(cond)
+#else
+#define DCHECK(cond) \
+  CVC4_FATAL_IF(false, __PRETTY_FUNCTION__, __FILE__, __LINE__)
+#endif /* CVC4_DEBUG */
+
+}  // namespace CVC4
+
+#endif /* __CVC4__CHECK_H */
index 5459340f6825e019fc58cc6368dad2a36fab206e..010eaf4e55100ee927fbe39f98e61883bcc5d648 100644 (file)
  **/
 
 #include "bitblaster_template.h"
+
 #include "cvc4_private.h"
+
+#include "base/cvc4_check.h"
 #include "options/bv_options.h"
 #include "prop/cnf_stream.h"
 #include "prop/sat_solver_factory.h"
@@ -155,8 +158,7 @@ AigBitblaster::AigBitblaster()
     d_satSolver = prop::SatSolverFactory::createCryptoMinisat(smtStatisticsRegistry(),
                                                               "AigBitblaster");
     break;
-  default:
-    Unreachable("Unknown SAT solver type");
+  default: CVC4_FATAL() << "Unknown SAT solver type";
   }
 }
 
index 38113c5126317482174ef23c301326aa0080484e..2dd1eddd74b95819b7916f0e976d5af0ec8c5263 100644 (file)
@@ -21,6 +21,7 @@
 #include <iostream>
 
 #include "base/cvc4_assert.h"
+#include "base/cvc4_check.h"
 #include "lib/clock_gettime.h"
 #include "util/ostream_util.h"
 
@@ -76,7 +77,7 @@ inline timespec& operator-=(timespec& a, const timespec& b) {
     nsec -= nsec_per_sec;
     ++a.tv_sec;
   }
-  Assert(nsec >= 0 && nsec < nsec_per_sec);
+  DCHECK(nsec >= 0 && nsec < nsec_per_sec);
   a.tv_nsec = nsec;
   return a;
 }
@@ -167,22 +168,11 @@ void StatisticsRegistry::registerStat(Stat* s)
 void StatisticsRegistry::unregisterStat(Stat* s)
 {
 #ifdef CVC4_STATISTICS_ON
-  try
-  {
-    PrettyCheckArgument(d_stats.find(s) != d_stats.end(),
-                        s,
-                        "Statistic `%s' was not registered with this registry.",
-                        s->getName().c_str());
-  }
-  catch (Exception& e)
-  {
-    std::cerr << "Failure in StatisticsRegistry::unregisterStat():" << e.what()
-              << std::endl;
-    abort();
-  }
-  d_stats.erase(s);
+  CHECK(s != nullptr);
+  CHECK(d_stats.erase(s) > 0) << "Statistic `" << s->getName()
+                              << "' was not registered with this registry.";
 #endif /* CVC4_STATISTICS_ON */
-}/* StatisticsRegistry::unregisterStat_() */
+} /* StatisticsRegistry::unregisterStat() */
 
 void StatisticsRegistry::flushStat(std::ostream &out) const {
 #ifdef CVC4_STATISTICS_ON
@@ -212,16 +202,7 @@ void TimerStat::start() {
 
 void TimerStat::stop() {
   if(__CVC4_USE_STATISTICS) {
-    try
-    {
-      PrettyCheckArgument(d_running, *this, "timer not running");
-    }
-    catch (Exception& e)
-    {
-      std::cerr << "Fatal failure in TimerStat::stop(): " << e.what()
-                << std::endl;
-      abort();
-    }
+    CHECK(d_running) << "timer not running";
     ::timespec end;
     clock_gettime(CLOCK_MONOTONIC, &end);
     d_data += end - d_start;
index 9f61ef031ade8e57ca0662da6e71a95afffa2f47..167100ff018df946e28ba78bc14aa5125525383b 100644 (file)
@@ -41,6 +41,7 @@ UNIT_TESTS += \
        context/cdvector_black \
        util/array_store_all_black \
        util/assert_white \
+       util/check_white \
        util/binary_heap_black \
        util/bitvector_black \
        util/datatype_black \
diff --git a/test/unit/util/check_white.h b/test/unit/util/check_white.h
new file mode 100644 (file)
index 0000000..e57afa6
--- /dev/null
@@ -0,0 +1,58 @@
+/*********************                                                        */
+/*! \file check_white.h
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Tim King
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2017 by the authors listed in the file AUTHORS
+ ** in the top-level source directory) and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief White box testing of check utilities.
+ **
+ ** White box testing of check utilities.
+ **/
+
+#include <cxxtest/TestSuite.h>
+
+#include <cstring>
+#include <string>
+
+#include "base/cvc4_check.h"
+
+using namespace std;
+using namespace CVC4;
+
+namespace {
+
+class CheckWhite : public CxxTest::TestSuite
+{
+ public:
+  const int kOne = 1;
+
+  // This test just checks that this statement compiles.
+  std::string TerminalCvc4Fatal() const
+  {
+    CVC4_FATAL() << "This is a test that confirms that CVC4_FATAL can be a "
+                    "terminal statement in a function that has a non-void "
+                    "return type.";
+  }
+
+  void testCheck() { CHECK(kOne >= 0) << kOne << " must be positive"; }
+  void testDCheck()
+  {
+    DCHECK(kOne == 1) << "always passes";
+#ifndef CVC4_ASSERTIONS
+    DCHECK(false) << "Will not be compiled in when CVC4_ASSERTIONS off.";
+#endif /* CVC4_ASSERTIONS */
+  }
+
+  void testPointerTypeCanBeTheCondition()
+  {
+    const int* one_pointer = &kOne;
+    CHECK(one_pointer);
+  }
+};
+
+}  // namespace