Fixes calls to SetVertexAttributeFormat with zero stride.
[0ad.git] / source / lib / debug.cpp
blob337b091e65a291f39bec9d77474f9f7c1f92fde6
1 /* Copyright (C) 2021 Wildfire Games.
3 * Permission is hereby granted, free of charge, to any person obtaining
4 * a copy of this software and associated documentation files (the
5 * "Software"), to deal in the Software without restriction, including
6 * without limitation the rights to use, copy, modify, merge, publish,
7 * distribute, sublicense, and/or sell copies of the Software, and to
8 * permit persons to whom the Software is furnished to do so, subject to
9 * the following conditions:
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 #include "precompiled.h"
24 #include "lib/debug.h"
26 #include "lib/alignment.h"
27 #include "lib/app_hooks.h"
28 #include "lib/fnv_hash.h"
29 #include "lib/sysdep/cpu.h" // cpu_CAS
30 #include "lib/sysdep/sysdep.h"
31 #include "lib/sysdep/vm.h"
33 #if OS_WIN
34 # include "lib/sysdep/os/win/wdbg_heap.h"
35 #endif
37 #include <cstdarg>
38 #include <cstring>
39 #include <cstdio>
41 namespace
44 // (NB: this may appear obscene, but deep stack traces have been
45 // observed to take up > 256 KiB)
46 constexpr std::size_t MESSAGE_SIZE = 512 * KiB / sizeof(wchar_t);
47 wchar_t g_MessageBuffer[MESSAGE_SIZE];
49 } // anonymous namespace
51 static const StatusDefinition debugStatusDefinitions[] = {
52 { ERR::SYM_NO_STACK_FRAMES_FOUND, L"No stack frames found" },
53 { ERR::SYM_UNRETRIEVABLE_STATIC, L"Value unretrievable (stored in external module)" },
54 { ERR::SYM_UNRETRIEVABLE, L"Value unretrievable" },
55 { ERR::SYM_TYPE_INFO_UNAVAILABLE, L"Error getting type_info" },
56 { ERR::SYM_INTERNAL_ERROR, L"Exception raised while processing a symbol" },
57 { ERR::SYM_UNSUPPORTED, L"Symbol type not (fully) supported" },
58 { ERR::SYM_CHILD_NOT_FOUND, L"Symbol does not have the given child" },
59 { ERR::SYM_NESTING_LIMIT, L"Symbol nesting too deep or infinite recursion" },
60 { ERR::SYM_SINGLE_SYMBOL_LIMIT, L"Symbol has produced too much output" },
61 { INFO::SYM_SUPPRESS_OUTPUT, L"Symbol was suppressed" }
63 STATUS_ADD_DEFINITIONS(debugStatusDefinitions);
66 // need to shoehorn printf-style variable params into
67 // the OutputDebugString call.
68 // - don't want to split into multiple calls - would add newlines to output.
69 // - fixing Win32 _vsnprintf to return # characters that would be written,
70 // as required by C99, looks difficult and unnecessary. if any other code
71 // needs that, implement GNU vasprintf.
72 // - fixed size buffers aren't nice, but much simpler than vasprintf-style
73 // allocate+expand_until_it_fits. these calls are for quick debug output,
74 // not loads of data, anyway.
76 // rationale: static data instead of std::set to allow setting at any time.
77 // we store FNV hash of tag strings for fast comparison; collisions are
78 // extremely unlikely and can only result in displaying more/less text.
79 static const size_t MAX_TAGS = 20;
80 static u32 tags[MAX_TAGS];
81 static size_t num_tags;
83 void debug_filter_add(const char* tag)
85 const u32 hash = fnv_hash(tag, strlen(tag)*sizeof(tag[0]));
87 // make sure it isn't already in the list
88 for(size_t i = 0; i < MAX_TAGS; i++)
89 if(tags[i] == hash)
90 return;
92 // too many already?
93 if(num_tags == MAX_TAGS)
95 DEBUG_WARN_ERR(ERR::LOGIC); // increase MAX_TAGS
96 return;
99 tags[num_tags++] = hash;
102 void debug_filter_remove(const char* tag)
104 const u32 hash = fnv_hash(tag, strlen(tag)*sizeof(tag[0]));
106 for(size_t i = 0; i < MAX_TAGS; i++)
108 if(tags[i] == hash) // found it
110 // replace with last element (avoid holes)
111 tags[i] = tags[MAX_TAGS-1];
112 num_tags--;
114 // can only happen once, so we're done.
115 return;
120 void debug_filter_clear()
122 std::fill(tags, tags+MAX_TAGS, 0);
125 bool debug_filter_allows(const char* text)
127 size_t i;
128 for(i = 0; ; i++)
130 // no | found => no tag => should always be displayed
131 if(text[i] == ' ' || text[i] == '\0')
132 return true;
133 if(text[i] == '|' && i != 0)
134 break;
137 const u32 hash = fnv_hash(text, i*sizeof(text[0]));
139 // check if entry allowing this tag is found
140 for(i = 0; i < MAX_TAGS; i++)
141 if(tags[i] == hash)
142 return true;
144 return false;
147 #undef debug_printf // allowing #defining it out
148 void debug_printf(const char* fmt, ...)
150 char buf[16384];
152 va_list ap;
153 va_start(ap, fmt);
154 const int numChars = vsprintf_s(buf, ARRAY_SIZE(buf), fmt, ap);
155 if (numChars < 0)
156 debug_break(); // poor man's assert - avoid infinite loop because ENSURE also uses debug_printf
157 va_end(ap);
159 debug_puts_filtered(buf);
162 void debug_puts_filtered(const char* text)
164 if(debug_filter_allows(text))
165 debug_puts(text);
169 //-----------------------------------------------------------------------------
171 Status debug_WriteCrashlog(const wchar_t* text)
173 // (avoid infinite recursion and/or reentering this function if it
174 // fails/reports an error)
175 enum State
177 IDLE,
178 BUSY,
179 FAILED
181 // note: the initial state is IDLE. we rely on zero-init because
182 // initializing local static objects from constants may happen when
183 // this is first called, which isn't thread-safe. (see C++ 6.7.4)
184 cassert(IDLE == 0);
185 static volatile intptr_t state;
187 if(!cpu_CAS(&state, IDLE, BUSY))
188 return ERR::REENTERED; // NOWARN
190 OsPath pathname = ah_get_log_dir()/"crashlog.txt";
191 FILE* f = sys_OpenFile(pathname, "w");
192 if(!f)
194 state = FAILED; // must come before DEBUG_DISPLAY_ERROR
195 DEBUG_DISPLAY_ERROR(L"Unable to open crashlog.txt for writing (please ensure the log directory is writable)");
196 return ERR::FAIL; // NOWARN (the above text is more helpful than a generic error code)
199 fputwc(0xFEFF, f); // BOM
200 fwprintf(f, L"%ls\n", text);
201 fwprintf(f, L"\n\n====================================\n\n");
203 // allow user to bundle whatever information they want
204 ah_bundle_logs(f);
206 fclose(f);
207 state = IDLE;
208 return INFO::OK;
212 //-----------------------------------------------------------------------------
213 // error message
214 //-----------------------------------------------------------------------------
217 // a stream with printf-style varargs and the possibility of
218 // writing directly to the output buffer.
219 class PrintfWriter
221 public:
222 PrintfWriter(wchar_t* buf, size_t maxChars)
223 : m_pos(buf), m_charsLeft(maxChars)
227 bool operator()(const wchar_t* fmt, ...) WPRINTF_ARGS(2)
229 va_list ap;
230 va_start(ap, fmt);
231 const int len = vswprintf(m_pos, m_charsLeft, fmt, ap);
232 va_end(ap);
233 if(len < 0)
234 return false;
235 m_pos += len;
236 m_charsLeft -= len;
237 return true;
240 wchar_t* Position() const
242 return m_pos;
245 size_t CharsLeft() const
247 return m_charsLeft;
250 void CountAddedChars()
252 const size_t len = wcslen(m_pos);
253 m_pos += len;
254 m_charsLeft -= len;
257 private:
258 wchar_t* m_pos;
259 size_t m_charsLeft;
263 // split out of debug_DisplayError because it's used by the self-test.
264 const wchar_t* debug_BuildErrorMessage(
265 const wchar_t* description,
266 const wchar_t* filename, int line, const char* func,
267 void* context, const wchar_t* lastFuncToSkip)
269 // retrieve errno (might be relevant) before doing anything else
270 // that might overwrite it.
271 wchar_t description_buf[100] = L"?";
272 wchar_t os_error[100] = L"?";
273 Status errno_equiv = StatusFromErrno(); // NOWARN
274 if(errno_equiv != ERR::FAIL) // meaningful translation
275 StatusDescription(errno_equiv, description_buf, ARRAY_SIZE(description_buf));
276 sys_StatusDescription(0, os_error, ARRAY_SIZE(os_error));
278 PrintfWriter writer(g_MessageBuffer, MESSAGE_SIZE);
280 // header
281 if(!writer(
282 L"%ls\r\n"
283 L"Location: %ls:%d (%hs)\r\n"
284 L"\r\n"
285 L"Call stack:\r\n"
286 L"\r\n",
287 description, filename, line, func
290 fail:
291 return L"(error while formatting error message)";
294 // append stack trace
295 Status ret = debug_DumpStack(writer.Position(), writer.CharsLeft(), context, lastFuncToSkip);
296 if(ret == ERR::REENTERED)
298 if(!writer(
299 L"While generating an error report, we encountered a second "
300 L"problem. Please be sure to report both this and the subsequent "
301 L"error messages."
303 goto fail;
305 else if(ret != INFO::OK)
307 wchar_t error_buf[100] = {'?'};
308 if(!writer(
309 L"(error while dumping stack: %ls)",
310 StatusDescription(ret, error_buf, ARRAY_SIZE(error_buf))
312 goto fail;
314 else // success
316 writer.CountAddedChars();
319 // append errno
320 if(!writer(
321 L"\r\n"
322 L"errno = %d (%ls)\r\n"
323 L"OS error = %ls\r\n",
324 errno, description_buf, os_error
326 goto fail;
328 return g_MessageBuffer;
332 //-----------------------------------------------------------------------------
333 // display error messages
334 //-----------------------------------------------------------------------------
336 // translates and displays the given strings in a dialog.
337 // this is typically only used when debug_DisplayError has failed or
338 // is unavailable because that function is much more capable.
339 // implemented via sys_display_msg; see documentation there.
340 void debug_DisplayMessage(const wchar_t* caption, const wchar_t* msg)
342 sys_display_msg(caption, msg);
346 // when an error has come up and user clicks Exit, we don't want any further
347 // errors (e.g. caused by atexit handlers) to come up, possibly causing an
348 // infinite loop. hiding errors isn't good, but we assume that whoever clicked
349 // exit really doesn't want to see any more messages.
350 static atomic_bool isExiting;
352 // this logic is applicable to any type of error. special cases such as
353 // suppressing certain expected WARN_ERRs are done there.
354 static bool ShouldSuppressError(atomic_bool* suppress)
356 if(isExiting)
357 return true;
359 if(!suppress)
360 return false;
362 if(*suppress == DEBUG_SUPPRESS)
363 return true;
365 return false;
368 static ErrorReactionInternal CallDisplayError(const wchar_t* text, size_t flags)
370 // first try app hook implementation
371 ErrorReactionInternal er = ah_display_error(text, flags);
372 // .. it's only a stub: default to normal implementation
373 if(er == ERI_NOT_IMPLEMENTED)
374 er = sys_display_error(text, flags);
376 return er;
379 static ErrorReaction PerformErrorReaction(ErrorReactionInternal er, size_t flags, atomic_bool* suppress)
381 const bool shouldHandleBreak = (flags & DE_MANUAL_BREAK) == 0;
383 switch(er)
385 case ERI_CONTINUE:
386 return ER_CONTINUE;
388 case ERI_BREAK:
389 // handle "break" request unless the caller wants to (doing so here
390 // instead of within the dlgproc yields a correct call stack)
391 if(shouldHandleBreak)
393 debug_break();
394 return ER_CONTINUE;
396 else
397 return ER_BREAK;
399 case ERI_SUPPRESS:
400 (void)cpu_CAS(suppress, 0, DEBUG_SUPPRESS);
401 return ER_CONTINUE;
403 case ERI_EXIT:
404 isExiting = 1; // see declaration
405 COMPILER_FENCE;
407 #if OS_WIN
408 // prevent (slow) heap reporting since we're exiting abnormally and
409 // thus probably leaking like a sieve.
410 wdbg_heap_Enable(false);
411 #endif
413 exit(EXIT_FAILURE);
415 case ERI_NOT_IMPLEMENTED:
416 default:
417 debug_break(); // not expected to be reached
418 return ER_CONTINUE;
422 ErrorReaction debug_DisplayError(const wchar_t* description,
423 size_t flags, void* context, const wchar_t* lastFuncToSkip,
424 const wchar_t* pathname, int line, const char* func,
425 atomic_bool* suppress)
427 // "suppressing" this error means doing nothing and returning ER_CONTINUE.
428 if(ShouldSuppressError(suppress))
429 return ER_CONTINUE;
431 // fix up params
432 // .. caller supports a suppress flag; set the corresponding flag so that
433 // the error display implementation enables the Suppress option.
434 if(suppress)
435 flags |= DE_ALLOW_SUPPRESS;
437 if(flags & DE_NO_DEBUG_INFO)
439 // in non-debug-info mode, simply display the given description
440 // and then return immediately
441 ErrorReactionInternal er = CallDisplayError(description, flags);
442 return PerformErrorReaction(er, flags, suppress);
445 // .. deal with incomplete file/line info
446 if(!pathname || pathname[0] == '\0')
447 pathname = L"unknown";
448 if(line <= 0)
449 line = 0;
450 if(!func || func[0] == '\0')
451 func = "?";
452 // .. _FILE__ evaluates to the full path (albeit without drive letter)
453 // which is rather long. we only display the base name for clarity.
454 const wchar_t* filename = path_name_only(pathname);
456 // display in output window; double-click will navigate to error location.
457 const wchar_t* text = debug_BuildErrorMessage(description, filename, line, func, context, lastFuncToSkip);
459 (void)debug_WriteCrashlog(text);
460 ErrorReactionInternal er = CallDisplayError(text, flags);
462 // TODO: use utf8 conversion without internal allocations.
463 debug_printf("%s(%d): %s\n", utf8_from_wstring(filename).c_str(), line, utf8_from_wstring(description).c_str());
465 // note: debug_break-ing here to make sure the app doesn't continue
466 // running is no longer necessary. debug_DisplayError now determines our
467 // window handle and is modal.
469 return PerformErrorReaction(er, flags, suppress);
473 // is errorToSkip valid? (also guarantees mutual exclusion)
474 enum SkipStatus
476 INVALID, VALID, BUSY
478 static intptr_t skipStatus = INVALID;
479 static Status errorToSkip;
480 static size_t numSkipped;
482 void debug_SkipErrors(Status err)
484 if(cpu_CAS(&skipStatus, INVALID, BUSY))
486 errorToSkip = err;
487 numSkipped = 0;
488 COMPILER_FENCE;
489 skipStatus = VALID; // linearization point
491 else
492 DEBUG_WARN_ERR(ERR::REENTERED);
495 size_t debug_StopSkippingErrors()
497 if(cpu_CAS(&skipStatus, VALID, BUSY))
499 const size_t ret = numSkipped;
500 COMPILER_FENCE;
501 skipStatus = INVALID; // linearization point
502 return ret;
504 else
506 DEBUG_WARN_ERR(ERR::REENTERED);
507 return 0;
511 static bool ShouldSkipError(Status err)
513 if(cpu_CAS(&skipStatus, VALID, BUSY))
515 numSkipped++;
516 const bool ret = (err == errorToSkip);
517 COMPILER_FENCE;
518 skipStatus = VALID;
519 return ret;
521 return false;
524 ErrorReaction debug_OnError(Status err, atomic_bool* suppress, const wchar_t* file, int line, const char* func)
526 CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];
527 (void)debug_CaptureContext(context);
529 if(ShouldSkipError(err))
530 return ER_CONTINUE;
532 const wchar_t* lastFuncToSkip = L"debug_OnError";
533 wchar_t buf[400];
534 wchar_t err_buf[200]; StatusDescription(err, err_buf, ARRAY_SIZE(err_buf));
535 swprintf_s(buf, ARRAY_SIZE(buf), L"Function call failed: return value was %lld (%ls)", (long long)err, err_buf);
536 return debug_DisplayError(buf, DE_MANUAL_BREAK, context, lastFuncToSkip, file,line,func, suppress);
539 ErrorReaction debug_OnAssertionFailure(const wchar_t* expr, atomic_bool* suppress, const wchar_t* file, int line, const char* func)
541 CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE];
542 (void)debug_CaptureContext(context);
544 const wchar_t* lastFuncToSkip = L"debug_OnAssertionFailure";
545 wchar_t buf[400];
546 swprintf_s(buf, ARRAY_SIZE(buf), L"Assertion failed: \"%ls\"", expr);
547 return debug_DisplayError(buf, DE_MANUAL_BREAK, context, lastFuncToSkip, file,line,func, suppress);