/***************************************************************************\ Now.h - Will Mengarini - Version 1.00 - Mo 23 Jan 95 When we code assert( ), we're saying that is true now, at this point in the control flow. This module implements a more powerful assert() that is targeted at industrial-strength software engineering environments, where automated regression testing is an expected part of a delivered system, & a system is typically developed by a multi- person project team whose members develop modules independently, test them (this is called "unit testing"), then integrate them with the rest of the system & regression test to ensure that the improvements haven't broken anything else (this is part of what's called "system testing"). Integration can be surprisingly traumatic, so engineers who have lived thru it have learned that it's best done frequently; it has become customary for large systems to attempt a complete link & system test every night so all modules that were improved during the day are checked for continued compatibility with the rest of this system (this is called "the nightly build"). Assert-replacement macros in this API have the form now#(), where # is 0-9. The # is a measure of the importance of the assertion; the greater the #, the more important the assertion is, & the greater the importance, the later in system development it stops being tested. Standard assert() is executed only #ifndef NDEBUG, so it doesn't get into ship code. However, sometimes assert()s are so important that they do need to ship, & the desire for a ship_assert() was the first of two needs that started the development of this module. The name "ship_assert" is too long, however; if it's too ponderous to type, there will be fewer assertions in the code, which is Bad. Here, a now8() is a ship_assert(). A now6() gets as far as system testing (the nightly build), a now4() only reaches unit testing, & a now2() is used while debugging local regions of code. The manifest constants UNIT_TEST, SYSTEM_TEST, & SHIP are used to determine behavior of now[468](); exactly one of them must be 1. (Un#defined constants are evaluated as 0 in conditional preprocessor directives; that's a POSIX standard.) UNIT_TEST is intended to be 1 only within the module being unit- tested, *not* within subordinate modules used by the module being unit- tested. For example, the Now module contains conditional compilation based on UNIT_TEST that causes now.cpp to to contain a main(){} that tests Now. Obviously, this is impermissible when Now is used as part of the unit-testing of other modules, as is intended to be possible. I suggest that most now#()s should be now6()s, which is analogous to the standard assert(); when the nightly build becomes too slow, selected now6()s in mature code can start being reduced to now4()s. The gaps in the # sequence can be given whatever definitions individual project teams need. For example, now5() could be defined to test its condition in the same circumstances in which now6() tests it, but to not compile the diagnostic output. This could substantially reduce the size of the nightly build while maintaining the full rigor (but also the increased run time) of the system test. As a notational convenience now() is defined here as synonymous with now6(), but this can easily be changed by project teams that need something else. A now2() is used for debugging local regions of code & must be enabled by setting REGION_NOW_LEVEL = 2 (which can be done using a debugger, since REGION_NOW_LEVEL is a volatile int). There are an analogous now1() & now3(). These macros are independent of UNIT_TEST, SYSTEM_TEST, & SHIP, so if it's necessary to run a debugger on ship code (which has no debugging information), it'll at least be possible to use now[123]() for data output. Note that it's commonplace to temporarily instrument code for debugging, then to remove all the instrumentation when the bug is found, partly because it's ugly, & partly because it's inefficient. A now2() avoids that. A now2() is useful just as documentation, describing the programmer's intentions; but it can be activated when those intentions are suspected of being unmet. The standard assert() just displays the text & location of the failed assertion. In a now8(), however, it's essential, & elsewhere it's often desirable, to be able to display an explanatory message as well. An arg that's just a char* wouldn't be powerful enough, since what's really wanted here is the full power of stream-object output. The other major motivation for the development of this module (the first was the need for a ship_assert()) was the need to be able to associate formatted parameterized type-safe error explanations with assert()s, often as afterthoughts when assert() failures that were "impossible" started happening with sufficient frequency to require detailed diagnostic output from each instance. Another very frustrating attribute of the standard assert() is that it always abends the program, making automated unit-testing of error-handling pathways impossible. What's really needed to fix this is exceptions, which are unavailable in the C++ for which this is being developed. The only way around that is to unwind the stack by hand, & now#() is intended for use in a development environment in which functions that would, in a full C++, return void, instead return an error code that's checked by their callers, &, if unanticipated, propagated up the stack. This would allow now8( !do_something_cool(),, ); //the ",," will be explained later as a possible invocation of such a function. This is part of why macros instead of functions are being used for now#(); there needs to be a return statement in the now#() code. All non-test code therefore must check the return value from void-like functions it calls, & abend if the return code is nonzero; however, self-testing code can call such functions with deliberately erroneous arguments or environments, & check without abending the unit test that they correctly catch the error. I considered using a global error code instead (the more common approach to this kind of problem). That has several difficulties. For one thing, it's not reentrant. For another, the syntax actually ends up worse, typically do_something_cool(); now8( !global_error_code ); It's even easier to forget the obligatory now6()s using the global_error_code strategy than using the returned-error-code strategy. Reentrancy could be achieved by making the error code a data member of some task object rather than a global value, but the syntax remains prone to omission of the check. Note that because returns are being used to simulate exceptions, now()s can't be used inside constructors or destructors. To deal with this, put all the nontrivial processing of a ctor or dtor inside a method. There is a problem here. Although now8( !do_something_cool(),, ); does work, it has a serious disadvantage from a standpoint of software engineering rigor. One of the more unfortunate gotchas with assert()s is that, because they disappear in ship code, so do any side effects they may have; so proper assert()s must never have side effects. The whole point of now8( !do_something_cool(),, ); is the side effect of running do_something_cool(), however, & the only reason this works is that it's a now8(), which persists into ship code. The problem is that a major goal of now#() is to allow tweaking the digits more or less at whim to get the desired balance of failsafing and efficiency; & this use of now8() doesn't fit that pattern, because its purpose is to have a side effect. This problem will go away when C++ compilers support exceptions, since now8( !do_something_cool(),, ); is only a way of implementing dtor-calling exceptions by hand; but still, it leads to the dangerous habit of coding now#()s with side effects, which should always be eschewed. Therefore, this specific use of now8(), as an exception substitute, is implemented here with a differently-named macro that's internally almost identical to now8(), but is intended to look different enough that it won't engender confusion about side-effects in assert()s & assert()-substitutes. What we want to say is that we're requiring the called method to succeed (which it indicates by returning 0), & if it doesn't we want to propagate the exception up the stack; succeed( do_something_cool(),, ); //the ",," will be explained later seems to express that well enough, conveniently also eliminating the potentially-confusing "!". As this is being developed, most C++ preprocessors don't support optional args for macros, & advocacy of a more powerful preprocessor is politically incorrect. However, a very important aspect of now6() is its ability to start out as the equivalent of assert(), be enhanced with diagnostic output when its importance is discovered, &, if appropriate, be converted to a ship_assert(), all without needing to recode the syntactic scaffolding that surrounds the condition. However, altho args may not be omitted, they may be empty; so three_arg_macro( warga, blegga, pizza ) & three_arg_macro( foo, spoo, ) are both syntactically acceptable to the preprocessor. The now#() macros take 3 args, of which the last 2 are optional: they may be omitted, altho their commas may not. The first arg is the condition; the second arg is an error code of type int (an enum is acceptable using the Borland C++ option "treat enums as ints"); & the third arg is diagnostic output. It's OK to omit the second arg but include the third. Parameterized diagnostic output was implemented in C with printf(), but this wasn't type-safe, motivating the development of stream objects. This now( i > 0,, "Invalid i: " << i << " (it must be positive)" ) is an example of what now() can do. Note that the output stream isn't specified in the third arg; that arg is actually just an expression fragment, to which the implementation will prepend an equivalent of "cerr <<". Such an assertion will typically begin life as now( i > 0,, ); if that were an assert(), it'd have to be replaced by an if() statement, possibly wrapped inside an #if directive, just to display the value of i. Also note that if complex user-defined objects are given stream-object output methods, it becomes just as syntactically convenient to display the entire relevant state of the program as it is to display an int. The diagnostic output doesn't actually need to go to cerr (standard error). Because now8() is a ship_assert(), it's mandatory to enable diagnostics that would be comprehensible to a product user. __LINE__ & __FILE__ shouldn't be in such output. Two identifiers of type are defined, userLog & debugLog; in ship code, debugLog can be directed to a log file, while userLog goes to some video output window. During development, it's usually satisfactory to include the line ostream &userLog = cerr, &debugLog = cerr; in one of the .cpp files. In ship code, __LINE__ & __FILE__ go to debugLog, along with a time stamp, so they're accessible to telephone tech support staff while not confusing users. The third arg to now8() goes to userLog. Because ship code assertion failures generally require far more boilerplate of the "phone this # for help" variety, a facility is provided for defining that boilerplate in one place. Two char arrays, shipCodeNowFailureHeader & shipCodeNowFailureFooter, must be defined by Now users. Because you don't want to discover that you forgot these definitions only when you're ready to ship, they're actually required to be defined in test code as well, but are not output. In ship code they surround the third arg to now8(). If you want newlines at the ends of those boilerplate segments (you might not if they're going into a an error box) you need to insert them yourself. The second arg to the now#() macros is an error code used in unwinding the stack to simulate throwing an exception. (Just abending doesn't run object destructors, which matters not only for resource recovery (eg avoiding memory leaks) in caught exceptions, but also for cleanup of the system state even when an abend is to occur, such as releasing database locks.) The now#() macros expect to be called from inside functions that return success/failure ints, where 0 denotes success. If a now#() macro's condition is false, it displays its own diagnostic output as well as arg 3, then executes a return statement with a nonzero value. (You could easily change this, either to throw an exception if you have them, or to call exit() (which, nota bene, calls the at_exit() registrations, whereas _exit() does not) if that's sufficient for your resource recovery.) The nonzero value defaults to a manifest constant named defaultErrorCode; it is returned if there is no 2nd arg, else the 2nd arg is returned. You can give a value to defaultErrorCode using any of the standard methods (a -D directive, a #define, etc); if you do none of these, this .h gives it a value of 5000. In summary, this is what you must do to use the Now module: --Either design your code so each function that uses the now#() macros returns a success/failure int, or hack the NOW() macro below (an internal macro used in implementing each of the now#() macros) to use your own failure protocol, such as calling exit() or throwing an exception. Note that functions that return non-success/failure values can easily be redefined to take reference args into which they store those values. --Include the line ostream &userLog = cerr, &debugLog = cerr; (or something more complicated) in one of your .cpp modules. Watch those ampersands; if you have a typedef convention, use it instead. --Define lines like char shipCodeNowFailureHeader[] = "Don't worry, but I have bad news:"; char shipCodeNowFailureFooter[] = "Everything will be all right."; in one of your .cpp files. There needn't be a corresponding declaration in any of your .h files. --Ensure that in every compilation, exactly one of the manifest constants UNIT_TEST, SYSTEM_TEST, & SHIP will be 1. --Begin by just replacing the form assert( condition ); with the form now( condition,, ); you can later add diagnostic output & error codes as needed. Always call functions that participate in stack-unwinding with succeed( function,, ); rather than now8( !function,, ); even tho they're equivalent; now#()s should never have side effects. That completes the specification of the Now module API. It should be all you need to read if you want to use the code without hacking it. If you want to make improvements, you can't start by reading the rest of this file; you must start with a header, "WM-Root.h", that I #include in all my code. It contains #defines & typedefs without understanding which my code is unreadable, & also has some explanation of my programming style. \***************************************************************************/ #ifndef NOW_H #define NOW_H //You should not be reading this unless you've already read "WM-Root.h". //If you haven't, stop now & read it. #include "WM-Root.h" //You can redefine the following constant if necessary for your own //error-handling protocol. The #ifndef allows this to be done on the //compiler's command line by users who don't want to hack the source. #ifndef defaultErrorCode #define defaultErrorCode 5000 #endif //The user must run all compilations with exactly one of the following //constants defined as 1. #if (UNIT_TEST + SYSTEM_TEST + SHIP) != 1 #error #endif //Verify consistency with the manifest constant used to control //the built-in assert() macro (see the system include file assert.h). #if NDEBUG != SHIP #error #endif //All the now#() macros will be implemented in terms of NOW(), an internal //macro used to factor out the importance-level-independent code. //NOW() always expands to generate debugging code; the now#()s decide whether //to expand to NOW() or not. //The implementations of now[123]() & now[468]() are fundamentally different //in that now[123]() decide whether to call NOW() using an if() statement, //whereas now[468]() use an #if directive. This is because the decision for //now[468]() is based entirely on values known at preprocessing time, //whereas the decision for now[123]() is based on a value that might not //be known until run time. //First comes the implementation of now[123](), which are expanded based //on the value of REGION_NOW_LEVEL. REGION_NOW_LEVEL is declared here as //a volatile int; the volatility ensures that code optimization won't //prevent you from modifying REGION_NOW_LEVEL in the debugger to enable //now[123]() in a region. However, for exactly this reason, the debugging //code for now[123]() is always generated even if the now() is not enabled; //it's just not executed if it's not enabled. In the event that you want to //leave now[123]()s in place while causing them to generate no code, you //can do it by #defining REGION_NOW_LEVEL as 0 in optimized code; that will //cause the if()'s condition to be based entirely on literals, to always be //false, & to therefore generate no code. This #define is checked for //before the declaration here. #ifndef REGION_NOW_LEVEL extern volatile I REGION_NOW_LEVEL; // = 0 in Now.cpp #endif #define now1( condition, errorCode, errorMsg )\ if( REGION_NOW_LEVEL >= 1 ) NOW( condition, errorCode, errorMsg ); #define now2( condition, errorCode, errorMsg )\ if( REGION_NOW_LEVEL >= 2 ) NOW( condition, errorCode, errorMsg ); #define now3( condition, errorCode, errorMsg )\ if( REGION_NOW_LEVEL >= 3 ) NOW( condition, errorCode, errorMsg ); //REGION_NOW_LEVEL is volatile so it can be changed in the debugger, but //if source is being frequently recompiled during debugging, it may be //preferable to change REGION_NOW_LEVEL in source. This at least can be //made a bit more convenient by obviating the need to remember to change it //back at the end of the code being scrutinized. Calling blockNowLevel(2) //sets REGION_NOW_LEVEL to 2 for the duration of the {} block, then sets //it back to its previous level when the block is exited. //Since that whole concept is invalid if REGION_NOW_LEVEL is #defined, //a wrapper checking for that must protect blockNowLevel()'s implementation. #ifndef REGION_NOW_LEVEL #define blockNowLevel(i) BlockLevel objectOfClassBlockLevel = (i) class BlockLevel { I oldLevel; public: BlockLevel( K I newLevel ){ oldLevel = REGION_NOW_LEVEL; REGION_NOW_LEVEL = newLevel; } ~BlockLevel(){ REGION_NOW_LEVEL = oldLevel; } }; #endif //#ifndef REGION_NOW_LEVEL //As a notational convenience, now() is defined as whichever of now[468]() //the project team most customarily uses. That's expected to be now6(). #define now( condition, errorCode, errorMsg )\ now6( condition, errorCode, errorMsg ) //If now[468]() are enabled by the current #defines, they expand to NOW() //calls; else they expand to nothing. #if UNIT_TEST #define now4( condition, errorCode, errorMsg )\ NOW( condition, errorCode, errorMsg ) #else #define now4( condition, errorCode, errorMsg ) #endif #if UNIT_TEST || SYSTEM_TEST #define now6( condition, errorCode, errorMsg )\ NOW( condition, errorCode, errorMsg ) #else #define now6( condition, errorCode, errorMsg ) #endif #if UNIT_TEST || SYSTEM_TEST || SHIP #define now8( condition, errorCode, errorMsg )\ NOW( condition, errorCode, errorMsg ) #else #define now8( condition, errorCode, errorMsg ) #endif //When succeed() is used to simulate exceptions, it typically produces //a cascade of Now violations at successively higher invocation levels. //This cascade is confusing unless flagged as such, which requires //a global variable to remember that it happened, & a global function //to clear it if the exception is caught before an abend. extern I cascadeOfNowsInProgress;//initialized to 0 in now.cpp V clearNow(); //It also helps to clarify that the failing "assertion" is just //an "assertion" that an action was successful: #define successful(action) !(action) #define succeed( action, errorCode, errorMsg )\ NOW( successful( action ), errorCode, errorMsg ) //In NOW(), either errorCode or errorMsg may be omitted from the invocation, //as long as their commas are present; but condition, unlike errorCode or //errorMsg, is guaranteed to be nonempty. (An empty condition wouldn't make //sense in terms of what the API is implementing, & so NOW() is coded to //generate a C++ syntax error if condition is empty.) //In NOW(), error logging is handled by an object rather than a function so //it can preserve a data state during syntactic shenanigans that are needed //to cope with the possibly-empty args. A class ErrorLog is defined for this, //& the non-empty data it will need, __FILE__, __LINE__, & the stringized //condition, are just passed as args to its constructor. Its destructor will //be run implicitly when the return statement in NOW() is executed, & those //two methods--constructor & destructor--take care of whatever output needs //to surround the user's output in the errorMsg arg. //The central shenanigan for dealing with empty args is to exploit the //ambiguous arity of "+": a possibly-empty arg can precede it without //rendering the resulting C++ invalid if the C++ makes sense whether the //"+" is unary or binary, & the emptiness of the arg can be tested by code //that does different things depending on that arity. //This is easiest with errorCode, which is either emptiness or a nonzero #; //the expression "errorCode + defaultErrorCode != defaultErrorCode" is true //if the user specified an errorCode, & the expression "errorCode + 0" is //a syntactically-safe way of referring to errorCode whether empty or not. //The problem with errorMsg is much more complex, because even a nonempty //errorMsg needn't be a valid C++ expression; it's an expression /fragment/, //to which an ostream insertion like "cerr <<" must be prepended. WM-Root.h //(#included at the top of now.h) defines an object named "nul" that comes //in useful in situations like this. It's basically a placeholder. To cope //with errorMsg, we can define the expression "errorMsg + nul" to always be //wellformed & meaningful by providing suitable overloadings of "+". //Note that errorCode & errorMsg can't be parenthesized the way macro args //usually are, because they might be empty, & because errorMsg might contain //ostream::operator <<(); "cerr << (string1 << string2)" parses wrong. (It //can actually be /wellformed/ under Borland 3.1; a literal string is a //pointer, which is implicitly convertible to an int, so "spoo" << "oops" //left-shifts the address of "spoo" by the integer value modulo 32 of the //address of "oops". This pointer is then output instead of the error msg.) #define NOW( condition, errorCode, errorMsg )\ if( !(condition) ){\ ErrorLog el( __FILE__, __LINE__, #condition );\ userLog << errorMsg + nul;\ ; return errorCode + defaultErrorCode != defaultErrorCode ? \ errorCode + 0 : defaultErrorCode;\ } //The ErrorLog destructor will need to know whether errorMsg was empty, since //I think it's unhackworthy for a module like this to require programmers to //explicitly code newlines at the end of args like errorMsg; they all require //one, so the module should provide it, but of course it must only be //provided if there /was/ an errorMsg, since it's also unhackworthy to just //output a spurious blank line. (The same argument doesn't apply to the //shipCodeNowFailure{Header,Footer} boilerplate since the programmer only //needs to code that once.) A global can keep track of whether errorMsg was //empty, & its value can be set by the "+" overloadings. extern I errorMsgNonemptyInCallOfNOW; //defined in now.cpp //In the event that errorMsg is empty, "userLog << errorMsg + nul" becomes //"userLog << + nul", which must be a wellformed NOP. Inserting "" into //an ostream is always a NOP, so "+ nul" can return "" & set the boolean. //(Note that nul is an object (the only object) of class Nul.) C *operator + ( Nul );//defined in now.cpp //This is the definition: // C *operator + ( Nul ){ // errorMsgNonemptyInCallOfNOW = 0; // return ""; // } //It needs to go in now.cpp since putting it here would generate a //separate copy of the code in each .cpp in which now.h was #included. //In the event that errorMsg is nonempty, "userLog << errorMsg + nul" must //become equivalent to "userLog << errorMsg". This can be achieved by //defining the expression fragment "+ nul" as a postfix identity function //defined on all data types. Like the unary "+" above, it must set the //boolean as a side effect. template< class T > T operator + ( T t, Nul ){ errorMsgNonemptyInCallOfNOW = 1; return t; } //Unlike ordinary function code, template code needs to go in a header //so each .cpp can decide how to instantiate it. //(I considered another approach to dealing with errorMsg: whether it's //empty or not can be used to overload a function of void. For example, // class EmptinessTester { // I itWasEmpty; // public: // EmptinessTester () { itWasEmpty = 1; } // EmptinessTester ( C * ){ itWasEmpty = 0; } // operator I () { return itWasEmpty; } // }; //would enable code like // if( EmptinessTester(#errorMsg) ) [...] //The problem is that because errorMsg is an expression fragment, //it must be stringized to produce a value that can overload a ctor; //& the necessary attribute of the stringize operator--that stringizing //an empty arg produces emptiness, not ""--is undocumented either in the //Borland C++ v3.1 Programmer's Guide p164 or Stroustrup 91 r.16.3.1, //& without it, the whole concept breaks. The template/Nul hack always works. //(Just as an aside, note that this definition of the stringize operator is //strictly more powerful than one that returns ""; if you want the latter //effect, you can get it with code like // #someArg "" //which, in the event that someArg is nonempty, will generate // "valueOfSomeArg" "" //which the preprocessor (Stroustrup 91 r.16.1) then concatenates.)) //The declarations for userLog & debugLog must have been seen by the time //class ErrorLog is declared. extern ostream &userLog, &debugLog; //user must define these //The constructor & destructor write a header & footer preceding & following //the user's errorMsg. Methods for doing that need to be separately compiled //(so they're in now.cpp rather than now.h) because they generate a lot of //code, so all the class definition here does is declare & call them. class ErrorLog { K C *errorFile, *condition; K I errorLine; public: ErrorLog ( K C *errorFile, K I errorLine, K C *condition ) : errorFile(errorFile) , errorLine(errorLine) , condition(condition) { writeLogHeader( errorFile, errorLine, condition ); } ~ErrorLog (){ writeLogFooterAndFlagCascade( errorFile, errorLine, condition ); } private: V writeLogHeader ( K C *errorFile, K I errorLine, K C *condition ) K; V writeLogFooterAndFlagCascade ( K C *errorFile, K I errorLine, K C *condition ) K; }; #endif //NOW_H