1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoDebugError SoDebugError.h Inventor/errors/SoDebugError.h
35   \brief The SoDebugError class is the internal debugging message passing mechanism.
36 
37   \ingroup errors
38 
39   This class basically serves two purposes:
40 
41   1) It is the message interface through which error and warning
42   conditions are passed to the programmer which is using the Coin
43   library API for building applications.  These messages are generated
44   when API methods are used in an incorrect manner, or if actions on
45   the library has caused it to enter an inconsistent state. Coin
46   programmers should then quickly be able to trace down errors in
47   their application code.
48 
49   For this service to be available, the Coin library must be compiled
50   with debugging information. For release builds, most of this
51   "programmer's safety net" is removed to gain maximum speed and to
52   use minimum space (build the smallest possible library).
53 
54   2) Coin application programmers can call the SoDebugError methods
55   within their own code as a convenient way of debugging application
56   code, replacing the usual cascades of fprintf() calls.
57 
58 
59   Coin supports an environment variable to set conditional
60   breakpoints.  The COIN_DEBUG_BREAK environment variable can be set
61   to any number of functions in the form of a list separated by commas
62   or spaces.  The functionnames must be given as
63   "classname::functioname" (ie without return type, parenthesis or
64   argument types or names).  If a debug message is posted from one of
65   those functions, your program will be stopped (using assert(0)).
66 
67   This can be useful if you want to get core-dumps or enter a debugger
68   whenever for instance a warning or an error is posted from some
69   function (e.g. "SbVec3f::normalize"). This feature is only enabled
70   in the debug version of Coin.
71 */
72 
73 // *************************************************************************
74 
75 #include <Inventor/errors/SoDebugError.h>
76 
77 #include <cassert>
78 #include <cstdarg>
79 #include <cstdio>
80 #include <cstdlib>
81 #include <cstring>
82 
83 #include <Inventor/C/errors/debugerror.h>
84 #include <Inventor/C/tidbits.h>
85 #include <Inventor/SbName.h>
86 #include <Inventor/SoType.h>
87 #include <Inventor/lists/SbList.h>
88 
89 #include "coindefs.h"
90 #include "tidbitsp.h"
91 
92 #ifndef COIN_WORKAROUND_NO_USING_STD_FUNCS
93 using std::strcmp;
94 using std::strlen;
95 using std::strcpy;
96 using std::strchr;
97 using std::memcpy;
98 #endif // !COIN_WORKAROUND_NO_USING_STD_FUNCS
99 
100 #ifdef HAVE_UNISTD_H
101   #include <sys/types.h>
102   #include <unistd.h>
103 #endif
104 
105 #if defined(_MSC_VER)
106   #if _MSC_VER >= COIN_MSVC_8_0_VERSION
107     #include <intrin.h>
108   #endif
109   #if _MSC_VER >= COIN_MSVC_7_0_VERSION
110     #define COIN_DEBUGGER_BREAK(x) __debugbreak()
111   #else
112     #define COIN_DEBUGGER_BREAK(x) __asm { int 3 }
113   #endif
114 #else
115   #if defined (MINGW32) || defined (CYGWIN)
116     #include <windows.h>
117     #define COIN_DEBUGGER_BREAK(x) ::DebugBreak()
118   #else
119     #if defined (_POSIX_VERSION)
120       #include <signal.h>
121       #define COIN_DEBUGGER_BREAK(x) ::raise(SIGINT)
122     #else
123       #define COIN_DEBUGGER_BREAK(x) assert(0 && (x))
124     #endif
125   #endif
126 #endif
127 
128 // *************************************************************************
129 
130 SoType SoDebugError::classTypeId STATIC_SOTYPE_INIT;
131 SoErrorCB * SoDebugError::callback = SoError::defaultHandlerCB;
132 void * SoDebugError::callbackData = NULL;
133 
134 // *************************************************************************
135 
136 #if COIN_DEBUG
137 
138 // variables for run-time breakpoints
139 static int num_breakpoints = 0;
140 static char ** breakpoints = NULL;
141 
142 extern "C" {
143 
144 static void
debug_break_cleanup(void)145 debug_break_cleanup(void)
146 {
147   for (int i = 0; i < num_breakpoints; i++) {
148     delete[] breakpoints[i];
149   }
150   delete[] breakpoints;
151   breakpoints = NULL;
152   num_breakpoints = 0;
153 }
154 
155 } // extern "C"
156 
157 #endif // COIN_DEBUG
158 
159 /*!
160   \enum SoDebugError::Severity
161 
162   Specifies the available severity levels of the debug messages.
163 */
164 
165 
166 // Documented for parent class.
167 void
initClass(void)168 SoDebugError::initClass(void)
169 {
170   SoDebugError::callback = SoError::defaultHandlerCB;
171   SoDebugError::callbackData = NULL;
172   SoDebugError::classTypeId =
173     SoType::createType(SoError::getClassTypeId(), "DebugError");
174 
175   // FIXME: the following "tokenizer" code could perhaps be factored
176   // out and moved to e.g. SbStringList? 20030820 mortene.
177 
178 #if COIN_DEBUG
179   const char * env = coin_getenv("COIN_DEBUG_BREAK");
180   if (env) {
181     num_breakpoints = 1;
182     const char * ptr = env;
183     while (*ptr) {
184       if (*ptr == ' ' || *ptr == ',') num_breakpoints++;
185       ptr++;
186     }
187     breakpoints = new char*[num_breakpoints];
188     coin_atexit(debug_break_cleanup, CC_ATEXIT_MSG_SUBSYSTEM);
189     const size_t envstrlen = strlen(env);
190     char * cpy = new char[envstrlen + 1];
191     (void)strcpy(cpy, env);
192     ptr = cpy;
193     const char * end = strchr(ptr, ' ');
194     const char * tst = strchr(ptr, ',');
195     if (end == NULL || (tst && tst < end)) end = tst;
196     if (end == NULL) end = strchr(ptr, '\0');
197     int i = 0;
198     while (end && i < num_breakpoints) {
199       const ptrdiff_t len = end - ptr;
200       breakpoints[i] = new char[len + 1];
201       (void)memcpy(breakpoints[i], ptr, len);
202       breakpoints[i][len] = '\0';
203       i++;
204       ptr = end+1;
205       if (ptr < (cpy + envstrlen)) {
206         end = strchr(ptr, ' ');
207         tst = strchr(ptr, ',');
208         if (end == NULL || (tst && tst < end)) end = tst;
209         if (end == NULL) end = strchr(ptr, 0);
210       }
211     }
212     num_breakpoints = i; // just in case parsing failed for some reason
213     delete[] cpy;
214   }
215 #endif // COIN_DEBUG
216 }
217 
218 void
callbackForwarder(const struct cc_debugerror * error,void * COIN_UNUSED_ARG (data))219 SoDebugError::callbackForwarder(const struct cc_debugerror * error,
220                             void * COIN_UNUSED_ARG(data)
221                             )
222 {
223   SoDebugError wrappederr;
224 
225   switch (cc_debugerror_get_severity(error)) {
226   case CC_DEBUGERROR_ERROR:
227     wrappederr.severity = SoDebugError::ERROR;
228     break;
229   case CC_DEBUGERROR_WARNING:
230     wrappederr.severity = SoDebugError::WARNING;
231     break;
232   case CC_DEBUGERROR_INFO:
233     wrappederr.severity = SoDebugError::INFO;
234     break;
235   default:
236     assert(FALSE);
237     break;
238   }
239 
240   const cc_string * dbgstr =
241     cc_error_get_debug_string(reinterpret_cast<const cc_error *>(error));
242   const char * dbgstrc = cc_string_get_text(dbgstr);
243   wrappederr.setDebugString(dbgstrc);
244 
245   assert(SoDebugError::callback != NULL);
246   SoDebugError::callback(&wrappederr, SoDebugError::callbackData);
247 }
248 
249 // Documented for parent class.
250 void
setHandlerCallback(SoErrorCB * const function,void * const data)251 SoDebugError::setHandlerCallback(SoErrorCB * const function, void * const data)
252 {
253   if (SoDebugError::callback == SoError::defaultHandlerCB) {
254     // The user is overriding the default handler, so set up a
255     // "converter" callback function that makes an SoDebugError out of
256     // an cc_debugerror and forwards control to the callback function
257     // given as an argument to setHandlerCallback().
258     cc_debugerror_set_handler_callback(
259        reinterpret_cast<cc_debugerror_cb *>(SoDebugError::callbackForwarder), NULL);
260   }
261 
262   SoDebugError::callback = function;
263   SoDebugError::callbackData = data;
264 }
265 
266 // Documented for parent class.
267 SoErrorCB *
getHandlerCallback(void)268 SoDebugError::getHandlerCallback(void)
269 {
270   return SoDebugError::callback;
271 }
272 
273 // Documented for parent class.
274 void *
getHandlerData(void)275 SoDebugError::getHandlerData(void)
276 {
277   return SoDebugError::callbackData;
278 }
279 
280 // Documented for parent class.
281 SoType
getClassTypeId(void)282 SoDebugError::getClassTypeId(void)
283 {
284   return SoDebugError::classTypeId;
285 }
286 
287 // Documented for parent class.
288 SoType
getTypeId(void) const289 SoDebugError::getTypeId(void) const
290 {
291   return SoDebugError::classTypeId;
292 }
293 
294 /*!
295   Returns severity level of current message.
296 
297   You can use this to filter out debug messages and only let warnings
298   and/or errors pass through to the end-user if you have set your own
299   handler callback.
300 
301   \sa setHandlerCallback()
302 */
303 SoDebugError::Severity
getSeverity(void) const304 SoDebugError::getSeverity(void) const
305 {
306   return this->severity;
307 }
308 
309 #if COIN_DEBUG
310 
311 static inline void
check_breakpoints(const char * source)312 check_breakpoints(const char * source)
313 {
314   for (int i = 0; i < num_breakpoints; i++) {
315     if (strcmp(breakpoints[i], source) == 0) {
316       COIN_DEBUGGER_BREAK("Coin debug break");
317     }
318   }
319 }
320 
321 #else // COIN_DEBUG
322 
323 static inline void
check_breakpoints(const char *)324 check_breakpoints(const char *)
325 {
326   // should be empty
327 }
328 
329 #endif // ! COIN_DEBUG
330 
331 
332 void
commonPostHandling(Severity severity,const char * type,const char * source,const SbString & s)333 SoDebugError::commonPostHandling(Severity severity, const char * type,
334                                  const char * source, const SbString & s)
335 {
336   SoDebugError error;
337   error.severity = severity;
338   error.setDebugString("Coin ");
339   error.appendToDebugString(type);
340   error.appendToDebugString(" in ");
341   error.appendToDebugString(source);
342   error.appendToDebugString("(): ");
343   error.appendToDebugString(s.getString());
344 
345   if (SoDebugError::callback != SoError::defaultHandlerCB) {
346     SoDebugError::callback(&error, SoDebugError::callbackData);
347   }
348   else {
349     error.handleError();
350   }
351 
352   check_breakpoints(source);
353 }
354 
355 #define SODEBUGERROR_POST(SEVERITY, TYPE) \
356   va_list args; \
357   va_start(args, format); \
358   SbString s; \
359   s.vsprintf(format, args); \
360   va_end(args); \
361  \
362   SoDebugError::commonPostHandling(SEVERITY, TYPE, source, s)
363 
364 
365 /*!
366   This method posts a message with severity level "ERROR". \a source should
367   be the name of the calling function.
368 */
369 void
post(const char * const source,const char * const format,...)370 SoDebugError::post(const char * const source, const char * const format, ...)
371 {
372   SODEBUGERROR_POST(ERROR, "error");
373 }
374 
375 /*!
376   This method posts a message with severity level "WARNING". \a source should
377   be the name of the calling function.
378 */
379 void
postWarning(const char * const source,const char * const format,...)380 SoDebugError::postWarning(const char * const source, const char * const format, ...)
381 {
382   SODEBUGERROR_POST(WARNING, "warning");
383 }
384 
385 /*!
386   This method posts a message with severity level "INFO". \a source should
387   be the name of the calling function.
388 */
389 void
postInfo(const char * const source,const char * const format,...)390 SoDebugError::postInfo(const char * const source, const char * const format, ...)
391 {
392   SODEBUGERROR_POST(INFO, "info");
393 }
394 
395 // Documented for parent class.
396 SoErrorCB *
getHandler(void * & data) const397 SoDebugError::getHandler(void * & data) const
398 {
399   // FIXME: why the heck isn't this a static method? Looks to me like
400   // an API bug. 20020526 mortene.
401 
402   data = SoDebugError::callbackData;
403   return SoDebugError::callback;
404 }
405 
406 #undef SODEBUGERROR_POST
407 #undef COIN_DEBUGGER_BREAK
408