1 /******************************************************************************\
2 * Copyright (c) 2016, Robert van Engelen, Genivia Inc. All rights reserved.    *
3 *                                                                              *
4 * Redistribution and use in source and binary forms, with or without           *
5 * modification, are permitted provided that the following conditions are met:  *
6 *                                                                              *
7 *   (1) Redistributions of source code must retain the above copyright notice, *
8 *       this list of conditions and the following disclaimer.                  *
9 *                                                                              *
10 *   (2) Redistributions in binary form must reproduce the above copyright      *
11 *       notice, this list of conditions and the following disclaimer in the    *
12 *       documentation and/or other materials provided with the distribution.   *
13 *                                                                              *
14 *   (3) The name of the author may not be used to endorse or promote products  *
15 *       derived from this software without specific prior written permission.  *
16 *                                                                              *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED *
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF         *
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO   *
20 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,       *
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  *
23 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,     *
24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR      *
25 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF       *
26 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
27 \******************************************************************************/
28 
29 /**
30 @file      debug.h
31 @brief     RE/flex debug logs and assertions
32 @author    Robert van Engelen - engelen@genivia.com
33 @copyright (c) 2016-2020, Robert van Engelen, Genivia Inc. All rights reserved.
34 @copyright (c) BSD-3 License - see LICENSE.txt
35 
36 Exploiting macro magic to simplify debug logging.
37 
38 Usage
39 -----
40 
41 Enable macro DEBUG to debug the compiled source code:
42 
43 | Source files compiled with	| DBGLOG(...) entry added to	|
44 | ----------------------------- | ----------------------------- |
45 | `c++ -DDEBUG`			| `DEBUG.log`			|
46 | `c++ -DDEBUG=TEST`		| `TEST.log`			|
47 | `c++ -DDEBUG= `		| `stderr`			|
48 
49 `DBGLOG(format, ...)` creates a timestamped log entry with a printf-formatted
50 message. The log entry is added to a log file or sent to `stderr` as specified:
51 
52 `DBGLOGN(format, ...)` creates a log entry without a timestamp.
53 
54 `DBGLOGA(format, ...)` appends the formatted string to the previous log entry.
55 
56 `DBGCHK(condition)` calls `assert(condition)` when compiled in DEBUG mode.
57 
58 The utility macro `DBGSTR(const char *s)` returns string `s` or `"(null)"` when
59 `s == NULL`.
60 
61 @note to temporarily enable debugging a specific block of code without globally
62 debugging all code, use a leading underscore, e.g. `_DBGLOG(format, ...)`.
63 This appends the debugging information to `DEBUG.log`.
64 
65 @warning Be careful to revert these statements by removing the leading
66 underscore for production-quality code.
67 
68 Example
69 -------
70 
71 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
72     #include <reflex/debug.h>
73 
74     int main(int argc, char *argv[])
75     {
76       FILE *fd;
77       DBGLOG("Program start");
78       if ((fd = fopen("foo.bar", "r")) == NULL)
79       {
80         DBGLOG("Error %d: %s ", errno, DBGSTR(strerror(errno)));
81         for (int i = 1; i < argc; ++i)
82           DBGLOGA(" %s", argv[1]);
83       }
84       else
85       {
86         DBGCHK(fd != NULL);
87         // OK, so go ahead to read foo.bar ...
88         // ...
89         fclose(fd);
90       }
91       DBGLOG("Program end");
92     }
93 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94 
95 Compiled with `-DDEBUG` this example logs the following messages in `DEBUG.log`:
96 
97 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.txt}
98     140201/225654.692194   example.cpp:11   Program has started
99     140201/225654.692564   example.cpp:15   Error 2: No such file or directory
100     140201/225654.692577   example.cpp:17   Program ended
101 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102 
103 The first column records the date (140201 is February 1, 2014) and the time
104 (225654 is 10:56PM + 54 seconds) with microsecond fraction. The second column
105 records the source code file name and the line number of the `DBGLOG` command.
106 The third column shows the printf-formatted message.
107 
108 The `DEBUG.log` file is created in the current directory when it does not
109 already exist.
110 
111 Techniques used:
112 
113 - Variadic macros with `__VA_ARGS__`.
114 - Standard predefined macros `__FILE__` and `__LINE__`.
115 - Macro "stringification": expand content of macro `DEBUG` as a string in a
116   macro body.
117 - `#if DEBUG + 0` to test whether macro `DEBUG` is set to a value, since
118   `DEBUG` is 1 when set without a value (for example at the command line).
119 - `"" __VA_ARGS__` forces `__VA_ARGS__` to start with a literal format string
120   (printf security advisory).
121 */
122 
123 #ifndef REFLEX_DEBUG_H
124 #define REFLEX_DEBUG_H
125 
126 #include <cassert>
127 #include <cstdio>
128 
129 /// If ASSERT not defined, make ASSERT a no-op
130 #ifndef ASSERT
131 #define ASSERT(c)
132 #endif
133 
134 #undef DBGLOG
135 #undef DBGLOGN
136 #undef DBGLOGA
137 
138 extern FILE *REFLEX_DBGFD_;
139 
140 extern "C" void REFLEX_DBGOUT_(const char *log, const char *file, int line);
141 
142 #define DBGXIFY(S) DBGIFY_(S)
143 #define DBGIFY_(S) #S
144 #if DEBUG + 0
145 # define DBGFILE "DEBUG.log"
146 #else
147 # define DBGFILE DBGXIFY(DEBUG) ".log"
148 #endif
149 #define DBGSTR(S) (S?S:"(NULL)")
150 #define _DBGLOG(...) \
151 ( REFLEX_DBGOUT_(DBGFILE, __FILE__, __LINE__), ::fprintf(REFLEX_DBGFD_, "" __VA_ARGS__), ::fflush(REFLEX_DBGFD_))
152 #define _DBGLOGN(...) \
153 ( ::fprintf(REFLEX_DBGFD_, "\n                                        " __VA_ARGS__), ::fflush(REFLEX_DBGFD_) )
154 #define _DBGLOGA(...) \
155 ( ::fprintf(REFLEX_DBGFD_, "" __VA_ARGS__), ::fflush(REFLEX_DBGFD_) )
156 
157 #ifdef DEBUG
158 
159 #define DBGCHK(c) assert(c)
160 
161 #define DBGLOG _DBGLOG
162 #define DBGLOGN _DBGLOGN
163 #define DBGLOGA _DBGLOGA
164 
165 #else
166 
167 #define DBGCHK(c) (void)0
168 
169 #define DBGLOG(...) (void)0
170 #define DBGLOGN(...) (void)0
171 #define DBGLOGA(...) (void)0
172 
173 #endif
174 
175 #endif
176