Boost logo

Boost :

From: John Torjo (john_at_[hidden])
Date: 2003-05-07 09:35:32


Hi all,

Here's how I want to implement the smart assert (its features).
I plan to develop a full-featured ASSERT/ ENFORCE:

Please share any comments you might have, any other wishes as well
(don't worry about the implementation)

BOOST_ASSERT:
(a full-featured ASSERT)

1. Syntax:
BOOST_ASSERT( expression)(variable_involved_in_expr_1)(variable_involved_in_
expr_2)...;

Example:
BOOST_ASSERT( (i > 1) || (j < 0) || (k != i)) (i)(j)(k);

In case the assertion fails, a meaningful error message will appear,
somewhat like:
[file]:[line] Assertion failed: (i > 1) || (j < 0) || (k != i)
i = 0
j = 1
k = 0
--------------------
2. The function to call on ASSERTion failed is not necessary std::abort(),
   can be customized - at RUNTIME. (see point 4.)
-------------------
3. In case the assertion fails, we have extra information that is to be
logged,
like, __LINE__, __FILE__, __FUNCTION__ whatever.

We have a default, but it can be customized (at compile-time)

This information is build into a context: the context of the assertion.
(for example, an assertion might have failed, and the user has chosen to
ignore
this assertion for the rest of the application; we'll somehow have a boolean
variable stating this, and will be embedded into this context)
-------------
4. In case an assertion has failed, two actions will occur:
- first, the assertion will be logged
- second, a message will be show to the user, and the user can choose from
multiple actions, like Ignore, Retry, Abort, etc.

These are SEPARATE actions, and are both customizable.
Customization can happen at RUNTIME.

In cases such as this, it's better to allow customizing at runtime,
since it allows greater flexibility. For instance, you might have a runtime
switch, allowing to either show all assertions, or just show the first
assertion, and if the user chooses Ignore, not to show assertions any
further - just log them (this would be great at the customer site!).

Below, you'll see that there are other things that can be customized at
runtime. I chose to allow such things since they don't hurt performance,
and they allow for a lot of flexibility.
---------------------
5. Every failed assertion is logged.
   You can customize where and how this information is logged (as said
above).

------------------------
6. Thread-safety. The ASSERT will be thread-safe.
However, this should be able to be turned off, since I will most likely use
boost::thread to implement it. In case the user has not compiled
boost::thread, should still be able to use this library.
(compile-time switch)
------------------------
7. Sometimes, you might want, in case an assertion fails, for a friendlier
message to be shown to the user (in other words, the FULL information is
logged, but the user should get a simpler message).
(note: in this case, besides the assertion data being logged, the friendlier
message will be logged as well)

This can be easily done, somewhat like this:
BOOST_ASSERT( pApp != 0)(pApp).msg( "Ooopsie, no application ;-)");
------------------------
8. In case an assertion fails, an Assertion Handler will be called, which
 should ask the user whether to continue, break in code, or whatever.

The default, will allow for:
- ignore
- ignore all
- abort
- retry (break in code)

For Win32, I will show a message box, for other platforms, output to cout
and read from cin.
(or of course, the user can set its own handler, see point 4.)
------------------------
9. (I'm not really sure about whether you'll find this very useful, though I
think it is)
(note that it's not that hard to implement)

We could have "named asserts".
An assert could have a name. If an assertion fails, the user could also have
an option to ignore all asserts related to this (or even better, the
'Ignore' button could do this by default).

If the assertion is named, and the user chose to ignore all related to it,
other assertions with the same name will be ignored as well.

I think this could be very useful when developing modules/ small libraries -
multiple asserts in such a module/library could all have the same name. If
such a failed assertion is ignored, it's very likely that others related to
it will pop up as well, and normally you would have to ignore all of them.

(you should note that even if a failed assertion is ignored, it will still
be logged!)
------------------------
10. specifying all ASSERT's arguments

When using BOOST_ASSERT, you should specify all arguments involved in the
assert; otherwise, if the assertion fails, you might not get enough
information.

Example:
     // OK - all 3 params have been cared for
     BOOST_ASSERT( (i > 1) || (j < 0) || (k != i) )(i)(j)(k);

     // bad - in case an assertion fails, not enough info is outputted!
     // (k is not outputted)
     BOOST_ASSERT( (i > 1) || (j < 0) || (k != i) )(i)(j);

(compile-time switch, can be on/off; on by default)
------------------------
11. Handle ASSERT recursively

ASSERT might be entered recursively, I will take this into considerance
------------------------
12. Persistence of ignoring assertions.
I will allow for ignored assertions to be "persistent". For instance, if the
user encounters a failed assertion, chooses ignore, the program continues to
run. It closes the program, re-runs it. The same assertion will fail.
However, I can realize that the assertion was ignored last time, and keep
ignoring it (useful at the client site!)
(runtime switch)
-------------------------
13. Multiple levels of assertion.
There might be several levels of assertion.
The normal (default) is Debug - which, in case an assertion fails will
trigger a message, blabla.
We can have a "fatal error" level, in which case an exception could be
triggered.
For instance, we could have:
BOOST_ASSERT( i < 1000)(i).level( fatal_err, "value too big!");
--------------------------
14. Handle simple assertions.
If assertion is "trivial" (it contains only one argument and no operators),
you don't need to specify it again.
That is:
BOOST_ASSERT(p)(p);
is equivalent to
BOOST_ASSERT(p);

Note that the following won't be recognized:
BOOST_ASSERT( p != 0);
(it should be
BOOST_ASSERT( p != 0)(p); )
------------------------
15. Custom printing
I'll allow for custom printing of variables (when an assertion fails).

The default is using "operator<<". However, you can customize it.

For instance, for a non-null pointer, you might want to print (some of) its
contents. Or, for an STL container, to print its elements (or at least its
size)
-------------------------
-------------------------

BOOST_ENFORCE
(see http://www.cuj.com/experts/2106/alexandr.htm unless you've already seen
it ;-))

BOOST_ENFORCE allows for enforcing a condition on a value. If that fails
(usually) an error will be thrown.

1. Syntax:
BOOST_ENFORCE( expression)[(variable_involved_in_expr_1)(variable_involved_i
n_expr_2)...];
(much like BOOST_ASSERT)

Note that BOOST_ENFORCE returns a value, which can further on be
manipulated.
Example:

// [1]
Widget *p = ...;
BOOST_ENFORCE(p)->do_something();

// [2]
const int available = HANDLE_ENFORCE(_read(file, buffer,
buflen))(file)(buffer)(buflen);

--------------------
2. works very-much like in Andrei's article.
   Very easy to create ENFORCE-like macros.

Example:
struct HandlePredicate
{
    static bool Wrong(long handle) { return handle == -1; }
};
#define HANDLE_ENFORCE(exp) ...

// usage in code:
const int available =
    HANDLE_ENFORCE(_read(file, buffer, buflen))(file)(buffer)(buflen);
---------------------
3. When an error message should be used, the syntax will be a little
changed:

Instead of:
   Widget* pWidget = MakeWidget(n);
   ENFORCE(pWidget)("Widget number ")(n)(" is null and it shouldn't!");

Use something similar to:
   ENFORCE(pWidget) FAILUREMSG_("Widget number ")(n)(" is null and it
shouldn't!");

Basically I don't like this too much and I would welcome any suggestions.
The problem is the following. If we were to use:
ENFORCE(pWidget)("Widget number ")(n)(" is null and it shouldn't!");

I wouldn't know which is a value to be pretty-printed
(like for BOOST_ASSERT, 'B_A(i != j)(i)(j);' - I will pretty print
'i=2\nj=2', for instance), and which is a message. So, I chose to precede
the message with FAILUREMSG_.
-------------
4. In case the assertion fails, we have extra information that is to be
logged,
like, __LINE__, __FILE__, __FUNCTION__ whatever.

By default, this is the same as BOOST_ASSERT's, but can be changed.
-------------
5. specifying all ENFORCE's arguments
(this is tha same as BOOST_ASSERT's point 10.)
-------------
6. Custom printing
(same as BOOS_ASSERT's point 15)
------------

So far, that's it!

Here's a sneak peek at what is to come:

// boost_enforce.h
#ifndef BOOST_ENFORCE_H
#define BOOST_ENFORCE_H

#include <string>
#include <sstream>
#include <stdexcept>

namespace boost {

// TODO - inner namespace: assert

struct DefaultPredicate
{
  template <class T>
  static bool Wrong(const T& obj)
  {
    return !obj;
  }
};

struct DefaultRaiser
{
    template <class T>
    static void Throw(const T&, const std::string& message, const char*
locus)
    {
        throw std::runtime_error(message + '\n' + locus);
    }
};

// note: NEVER use directly! TODO: make private
struct Enforcer_Helper {};
inline Enforcer_Helper make_Enforcer_Helper() { return Enforcer_Helper(); }

template<typename Ref, typename P, typename R>
class Enforcer
{
public:
    Enforcer(Ref t, const char* locus, int nLine) : locus_(P::Wrong(t) ?
locus : 0),
        BOOST_SMART_ENFORCE_A( *this),
        BOOST_SMART_ENFORCE_B( t)
    {
        if ( locus_)
            std::clog << locus_ << nLine << std::endl;
    }

    template< class type>
    Enforcer& log_current_var( const type & val, const char * str)
    {
        if ( locus_)
        {
            // Here we have time; an exception will be thrown
            std::clog << ", " << str << "=" << val;
        }
        return *this;
    }

    operator Ref() const
    {
        if (locus_) R::Throw(BOOST_SMART_ENFORCE_B, msg_, locus_);
        return BOOST_SMART_ENFORCE_B;
    }

public:
    Enforcer& BOOST_SMART_ENFORCE_A;
    Ref BOOST_SMART_ENFORCE_B;

private:
    std::string msg_;
    const char* const locus_;
};

template <class P, class R, typename T>
inline Enforcer<const T&, P, R>
MakeEnforcer(const T& t, const char* locus, int nLine)
{
    return Enforcer<const T&, P, R>(t, locus, nLine);
}

template <class P, class R, typename T>
inline Enforcer<T&, P, R>
MakeEnforcer(T& t, const char* locus, int nLine)
{
    return Enforcer<T&, P, R>(t, locus, nLine);
}

#define STRINGIZE(expr) STRINGIZE_HELPER(expr)
#define STRINGIZE_HELPER(expr) #expr

#define ENFORCE(exp) \
    boost::MakeEnforcer<boost::DefaultPredicate,
boost::DefaultRaiser>((exp), "Expression '" #exp "' failed in '" \
    __FILE__ "', line: ", __LINE__).BOOST_SMART_ENFORCE_A

#define BOOST_SMART_ENFORCE_A(x) BOOST_SMART_ENFORCE_OP(x, B)
#define BOOST_SMART_ENFORCE_B(x) BOOST_SMART_ENFORCE_OP(x, A)

#define BOOST_SMART_ENFORCE_OP(x, next) \
    BOOST_SMART_ENFORCE_A.log_current_var(x, #x).BOOST_SMART_ENFORCE_ ##
next \
    /**/

} // namespace boost

#endif

// boost_assert.h
#ifndef BOOST_ASSERT_H
#define BOOST_ASSERT_H

#include <stdlib.h>
#include <iostream>

namespace boost {

// TODO - inner namespace: assert

enum { BOOST_ASSERT_A, BOOST_ASSERT_B };

struct invariant {
    inline ~invariant() {
        abort();
        return;
    }
};

#define BOOST_ASSERT(expr) \
    if ((expr)) ; else \
        boost::invariant(), \
        std::clog << "\nASSERT failure: " #expr << std::endl , \
        boost::BOOST_ASSERT_A \
    /**/

#define BOOST_ASSERT_A(x) BOOST_ASSERT_OP(x, B)
#define BOOST_ASSERT_B(x) BOOST_ASSERT_OP(x, A)

#define BOOST_ASSERT_OP(x, next) \
    BOOST_ASSERT_A, \
    std::clog << #x "=" << (x) << std::endl, \
    boost::BOOST_ASSERT_ ## next \
    /**/

} // boost

#endif

// better_assert.cpp
//

#pragma warning (disable : 4786)

#include <iostream>
#include "boost_enforce.h"
#include "boost_assert.h"
#include <io.h> // only on Windows platforms,

/* for Unix, instead of #include <io.h>

#include <unistd.h>
#define _read read
*/

struct Test
{
    Test( int i) : m_i( i)
    {}
    void print_i()
    { std::cout << "test=" << m_i << std::endl; }
private:
    int m_i;
};

struct HandlePredicate
{
    static bool Wrong(long handle)
    {
        return handle == -1;
    }
};
#define HANDLE_ENFORCE(exp) \
    boost::MakeEnforcer<HandlePredicate, boost::DefaultRaiser>((exp),
"Expression '" #exp "' failed in '" \
    __FILE__ "', line: ", __LINE__).BOOST_SMART_ENFORCE_A

void test_enforce()
{
    int i = 0;
    int *p = &i;

    int * p2 = ENFORCE( p)(p);
    *p2 = 1;

    try {
        int * p3 = ENFORCE( (int*)0);
    } catch( ...)
    {}

    Test t( 6);
    Test * pTest = &t;
    ENFORCE(pTest)(pTest)->print_i();

    // will of course faile
    int file = -1;
    void *buffer = NULL;
    int buflen = 40;
    try {
        const int available =
            HANDLE_ENFORCE(_read(file, buffer,
buflen))(file)(buffer)(buflen);
    }
    catch (...)
    {}

}

void test_assert()
{
    int i, j, k;
    k = 1;
    i = 3;
    j = 2;
    BOOST_ASSERT( (i < j) || (i == 3) || (j == 1) ) (i)(j);
    BOOST_ASSERT( (i < j) || (i < 0) || (k == -1) ) (i)(j)(k);
}

int main()
{
    test_enforce();
    test_assert();
    std::cin.get();
    return 0;
}

--
John Torjo
-- "Practical C++" column writer for builder.com.com
Freelancer, C++ consultant
mailto:john_at_[hidden]

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk