Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
debug_stl.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
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:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
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.
21  */
22 
23 /*
24  * portable debugging helper functions specific to the STL.
25  */
26 
27 #include "precompiled.h"
28 #include "lib/debug_stl.h"
29 
30 #include <deque>
31 #include <map>
32 #include <set>
33 #include <cassert>
34 #include <list>
35 
36 #include "lib/regex.h"
37 
39  { ERR::STL_CNT_UNKNOWN, L"Unknown STL container type_name" },
40  { ERR::STL_CNT_UNSUPPORTED, L"Unsupported STL container" },
41  { ERR::STL_CNT_INVALID, L"Container type is known but contents are invalid" }
42 };
43 STATUS_ADD_DEFINITIONS(debugStlStatusDefinitions);
44 
45 
46 // used in debug_stl_simplify_name.
47 // note: wcscpy_s is safe because replacement happens in-place and
48 // <what> is longer than <with> (otherwise, we wouldn't be replacing).
49 #define REPLACE(what, with)\
50  else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\
51  {\
52  src += ARRAY_SIZE(what)-1-1; /* see preincrement rationale*/\
53  wcscpy_s(dst, ARRAY_SIZE(with), (with));\
54  dst += ARRAY_SIZE(with)-1;\
55  }
56 #define STRIP(what)\
57  else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\
58  {\
59  src += ARRAY_SIZE(what)-1-1;/* see preincrement rationale*/\
60  }
61 #define STRIP_NESTED(what)\
62  else if(!wcsncmp(src, (what), ARRAY_SIZE(what)-1))\
63  {\
64  /* remove preceding comma (if present) */\
65  if(src != name && src[-1] == ',')\
66  dst--;\
67  src += ARRAY_SIZE(what)-1;\
68  /* strip everything until trailing > is matched */\
69  ENSURE(nesting == 0);\
70  nesting = 1;\
71  }
72 
73 // reduce complicated STL names to human-readable form (in place).
74 // e.g. "std::basic_string<char, char_traits<char>, std::allocator<char> >" =>
75 // "string". algorithm: strip undesired strings in one pass (fast).
76 // called from symbol_string_build.
77 //
78 // see http://www.bdsoft.com/tools/stlfilt.html and
79 // http://www.moderncppdesign.com/publications/better_template_error_messages.html
80 wchar_t* debug_stl_simplify_name(wchar_t* name)
81 {
82  // used when stripping everything inside a < > to continue until
83  // the final bracket is matched (at the original nesting level).
84  int nesting = 0;
85 
86  const wchar_t* src = name-1; // preincremented; see below.
87  wchar_t* dst = name;
88 
89  // for each character: (except those skipped as parts of strings)
90  for(;;)
91  {
92  wchar_t c = *(++src);
93  // preincrement rationale: src++ with no further changes would
94  // require all comparisons to subtract 1. incrementing at the
95  // end of a loop would require a goto, instead of continue
96  // (there are several paths through the loop, for speed).
97  // therefore, preincrement. when skipping strings, subtract
98  // 1 from the offset (since src is advanced directly after).
99 
100  // end of string reached - we're done.
101  if(c == '\0')
102  {
103  *dst = '\0';
104  break;
105  }
106 
107  // we're stripping everything inside a < >; eat characters
108  // until final bracket is matched (at the original nesting level).
109  if(nesting)
110  {
111  if(c == '<')
112  nesting++;
113  else if(c == '>')
114  {
115  nesting--;
116  ENSURE(nesting >= 0);
117  }
118  continue;
119  }
120 
121  // start if chain (REPLACE and STRIP use else if)
122  if(0) {}
123  else if(!wcsncmp(src, L"::_Node", 7))
124  {
125  // add a space if not already preceded by one
126  // (prevents replacing ">::_Node>" with ">>")
127  if(src != name && src[-1] != ' ')
128  *dst++ = ' ';
129  src += 7;
130  }
131  REPLACE(L"unsigned short", L"u16")
132  REPLACE(L"unsigned int", L"size_t")
133  REPLACE(L"unsigned __int64", L"u64")
134  STRIP(L",0> ")
135  // early out: all tests after this start with s, so skip them
136  else if(c != 's')
137  {
138  *dst++ = c;
139  continue;
140  }
141  REPLACE(L"std::_List_nod", L"list")
142  REPLACE(L"std::_Tree_nod", L"map")
143  REPLACE(L"std::basic_string<char, ", L"string<")
144  REPLACE(L"std::basic_string<__wchar_t, ", L"wstring<")
145  REPLACE(L"std::basic_string<unsigned short, ", L"wstring<")
146  STRIP(L"std::char_traits<char>, ")
147  STRIP(L"std::char_traits<unsigned short>, ")
148  STRIP(L"std::char_traits<__wchar_t>, ")
149  STRIP(L"std::_Tmap_traits")
150  STRIP(L"std::_Tset_traits")
151  STRIP_NESTED(L"std::allocator<")
152  STRIP_NESTED(L"std::less<")
153  STRIP_NESTED(L"stdext::hash_compare<")
154  STRIP(L"std::")
155  STRIP(L"stdext::")
156  else
157  *dst++ = c;
158  }
159 
160  return name;
161 }
162 
163 
164 //-----------------------------------------------------------------------------
165 // STL container debugging
166 //-----------------------------------------------------------------------------
167 
168 // provide an iterator interface for arbitrary STL containers; this is
169 // used to display their contents in stack traces. their type and
170 // contents aren't known until runtime, so this is somewhat tricky.
171 //
172 // we assume STL containers aren't specialized on their content type and
173 // use their int instantiations's memory layout. vector<bool> will therefore
174 // not be displayed correctly, but it is frowned upon anyway (since
175 // address of its elements can't be taken).
176 // to be 100% correct, we'd have to write an Any_container_type__element_type
177 // class for each combination, but that is clearly infeasible.
178 //
179 // containers might still be uninitialized when we call get_container_info on
180 // them. we need to check if they are IsValid and only then use their contents.
181 // to that end, we derive a validator class from each container,
182 // cast the container's address to it, and call its IsValid() method.
183 //
184 // checks performed include: is size() realistic; does begin() come before
185 // end(), etc. we need to leverage all invariants because the values are
186 // random in release mode.
187 //
188 // we sometimes need to access protected members of the STL containers.
189 // granting access via friend is not possible since the system headers
190 // must not be changed. that leaves us with two alternatives:
191 // 1) write a 'shadow' class that has the same memory layout. this would
192 // free us from the ugly Dinkumware naming conventions, but requires
193 // more maintenance when the STL implementation changes.
194 // 2) derive from the container. while not entirely bulletproof due to the
195 // lack of virtual dtors, this is safe in practice because pointers are
196 // neither returned to users nor freed. the only requirement is that
197 // classes must not include virtual functions, because a vptr would
198 // change the memory layout in unknown ways.
199 //
200 // it is rather difficult to abstract away implementation details of various
201 // STL versions. we currently only really support Dinkumware due to
202 // significant differences in the implementations of set, map and string.
203 
204 
205 //----------------------------------------------------------------------------
206 // standard containers
207 
208 // base class (slightly simplifies code by providing default implementations
209 // that can be used for most containers).
210 // Container is the complete type of the STL container (we can't pass this
211 // as a template because Dinkumware _Tree requires different parameters)
212 template<class Container>
213 struct ContainerBase : public Container
214 {
215  bool IsValid(size_t UNUSED(el_size)) const
216  {
217  return true;
218  }
219 
220  size_t NumElements(size_t UNUSED(el_size)) const
221  {
222  return this->size();
223  }
224 
225  static const u8* DereferenceAndAdvance(typename Container::iterator& it, size_t UNUSED(el_size))
226  {
227  const u8* p = (const u8*)&*it;
228  ++it;
229  return p;
230  }
231 };
232 
233 
234 struct Any_deque : public ContainerBase<std::deque<int> >
235 {
236 #if STL_DINKUMWARE == 405
237 
238  bool IsValid(size_t el_size) const
239  {
240  const size_t el_per_bucket = ElementsPerBucket(el_size);
241  // initial element is beyond end of first bucket
242  if(_Myoff >= el_per_bucket)
243  return false;
244  // more elements reported than fit in all buckets
245  if(_Mysize > _Mapsize * el_per_bucket)
246  return false;
247 
248  return true;
249  }
250 
251  static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size)
252  {
253  struct Iterator : public iterator
254  {
255  Any_deque& Container() const
256  {
257  return *(Any_deque*)_Mycont;
258  }
259 
260  size_t CurrentIndex() const
261  {
262  return _Myoff;
263  }
264  };
265 
266  Iterator& it = *(Iterator*)&stl_it;
267  Any_deque& container = it.Container();
268  const size_t currentIndex = it.CurrentIndex();
269  const u8* p = container.GetNthElement(currentIndex, el_size);
270  ++it;
271  return p;
272  }
273 
274 private:
275  static size_t ElementsPerBucket(size_t el_size)
276  {
277  return std::max(16u / el_size, (size_t)1u); // see _DEQUESIZ
278  }
279 
280  const u8* GetNthElement(size_t i, size_t el_size) const
281  {
282  const size_t el_per_bucket = ElementsPerBucket(el_size);
283  const size_t bucket_idx = i / el_per_bucket;
284  ENSURE(bucket_idx < _Mapsize);
285  const size_t idx_in_bucket = i - bucket_idx * el_per_bucket;
286  ENSURE(idx_in_bucket < el_per_bucket);
287  const u8** map = (const u8**)_Map;
288  const u8* bucket = map[bucket_idx];
289  const u8* p = bucket + idx_in_bucket*el_size;
290  return p;
291  }
292 
293 #endif
294 };
295 
296 
297 struct Any_list : public ContainerBase<std::list<int> >
298 {
299 };
300 
301 
302 #if STL_DINKUMWARE == 405
303 
304 template<class _Traits>
305 struct Any_tree : public std::_Tree<_Traits>
306 {
307  Any_tree() // (required because default ctor cannot be generated)
308  {
309  }
310 
311  bool IsValid(size_t UNUSED(el_size)) const
312  {
313  return true;
314  }
315 
316  size_t NumElements(size_t UNUSED(el_size)) const
317  {
318  return size();
319  }
320 
321  static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size)
322  {
323  struct Iterator : public const_iterator
324  {
325  _Nodeptr Node() const
326  {
327  return _Ptr;
328  }
329 
330  void SetNode(_Nodeptr node)
331  {
332  _Ptr = node;
333  }
334  };
335 
336  Iterator& it = *(Iterator*)&stl_it;
337  _Nodeptr node = it.Node();
338  const u8* p = (const u8*)&*it;
339 
340  // end() shouldn't be incremented, don't move
341  if(_Isnil(node, el_size))
342  return p;
343 
344  // return smallest (leftmost) node of right subtree
345  _Nodeptr _Pnode = _Right(node);
346  if(!_Isnil(_Pnode, el_size))
347  {
348  while(!_Isnil(_Left(_Pnode), el_size))
349  _Pnode = _Left(_Pnode);
350  }
351  // climb looking for right subtree
352  else
353  {
354  while (!_Isnil(_Pnode = _Parent(node), el_size) && node == _Right(_Pnode))
355  node = _Pnode; // ==> parent while right subtree
356  }
357  it.SetNode(_Pnode);
358  return p;
359  };
360 
361 private:
362  // return reference to the given node's nil flag.
363  // reimplemented because this member is stored after _Myval, so it's
364  // dependent on el_size.
365  static _Charref _Isnil(_Nodeptr _Pnode, size_t el_size)
366  {
367  const u8* p = (const u8*)&_Pnode->_Isnil; // correct for int specialization
368  p += el_size - sizeof(value_type); // adjust for difference in el_size
369  assert(*p <= 1); // bool value
370  return (_Charref)*p;
371  }
372 };
373 
374 
375 struct Any_map : public Any_tree<std::_Tmap_traits<int, int, std::less<int>, std::allocator<std::pair<const int, int> >, false> >
376 {
377 };
378 
379 
380 struct Any_multimap : public Any_map
381 {
382 };
383 
384 
385 struct Any_set: public Any_tree<std::_Tset_traits<int, std::less<int>, std::allocator<int>, false> >
386 {
387 };
388 
389 
390 struct Any_multiset: public Any_set
391 {
392 };
393 
394 #endif
395 
396 
397 struct Any_vector: public ContainerBase<std::vector<int> >
398 {
399  bool IsValid(size_t UNUSED(el_size)) const
400  {
401  // more elements reported than reserved
402  if(size() > capacity())
403  return false;
404  // front/back pointers incorrect
405  if(&front() > &back())
406  return false;
407  return true;
408  }
409 
410 #if STL_DINKUMWARE == 405
411 
412  size_t NumElements(size_t el_size) const
413  {
414  // vectors store front and back pointers and calculate their
415  // element count as the difference between them. since we are
416  // derived from a template specialization, the pointer arithmetic
417  // is incorrect. we fix it by taking el_size into account.
418  return ((u8*)_Mylast - (u8*)_Myfirst) * el_size;
419  }
420 
421  static const u8* DereferenceAndAdvance(iterator& stl_it, size_t el_size)
422  {
423  struct Iterator : public const_iterator
424  {
425  void Advance(size_t numBytes)
426  {
427  (u8*&)_Myptr += numBytes;
428  }
429  };
430 
431  Iterator& it = *(Iterator*)&stl_it;
432  const u8* p = (const u8*)&*it;
433  it.Advance(el_size);
434  return p;
435  }
436 
437 #endif
438 };
439 
440 
441 #if STL_DINKUMWARE == 405
442 
443 struct Any_basic_string : public ContainerBase<std::string>
444 {
445  bool IsValid(size_t el_size) const
446  {
447  // less than the small buffer reserved - impossible
448  if(_Myres < (16/el_size)-1)
449  return false;
450  // more elements reported than reserved
451  if(_Mysize > _Myres)
452  return false;
453  return true;
454  }
455 };
456 
457 #endif
458 
459 
460 //
461 // standard container adapters
462 //
463 
464 // debug_stl_get_container_info makes sure this was actually instantiated with
465 // container = deque as we assume.
466 struct Any_queue : public Any_deque
467 {
468 };
469 
470 
471 // debug_stl_get_container_info makes sure this was actually instantiated with
472 // container = deque as we assume.
473 struct Any_stack : public Any_deque
474 {
475 };
476 
477 
478 //-----------------------------------------------------------------------------
479 
480 // generic iterator - returns next element. dereferences and increments the
481 // specific container iterator stored in it_mem.
482 template<class T> const u8* stl_iterator(void* it_mem, size_t el_size)
483 {
484  typedef typename T::iterator iterator;
485  iterator& stl_it = *(iterator*)it_mem;
486  return T::DereferenceAndAdvance(stl_it, el_size);
487 }
488 
489 
490 // basic sanity checks that apply to all containers.
491 template<class T>
492 static bool IsContainerValid(const T& t, size_t el_count)
493 {
494  // note: don't test empty() because vector's implementation of it
495  // depends on el_size.
496 
497  // size must be reasonable
498  if(el_count > 0x1000000)
499  return false;
500 
501  if(el_count != 0)
502  {
503  // valid pointer
504  const u8* front = (const u8*)&*t.begin(); // (note: map doesn't have front)
505  if(debug_IsPointerBogus(front))
506  return false;
507 
508  // note: don't test back() because that depends on el_size and
509  // requires container-specific code.
510  }
511 
512  return true;
513 }
514 
515 // check if the container is IsValid and return # elements and an iterator;
516 // this is instantiated once for each type of container.
517 // we don't do this in the Any_* ctors because we need to return bool IsValid and
518 // don't want to throw an exception (may confuse the debug code).
519 template<class T> bool get_container_info(const T& t, size_t size, size_t el_size,
520  size_t& el_count, DebugStlIterator& el_iterator, void* it_mem)
521 {
522  typedef typename T::iterator iterator;
523  typedef typename T::const_iterator const_iterator;
524 
525  ENSURE(sizeof(T) == size);
526  ENSURE(sizeof(iterator) < DEBUG_STL_MAX_ITERATOR_SIZE);
527 
528  el_count = t.NumElements(el_size);
529 
530  // bail if the container is uninitialized/invalid.
531  if(!IsContainerValid(t, el_count))
532  return false;
533 
534  el_iterator = stl_iterator<T>;
535 
536  // construct a copy of begin() at it_mem. placement new is necessary
537  // because VC8's secure copy ctor apparently otherwise complains about
538  // invalid values in the (uninitialized) destination memory.
539  new(it_mem) const_iterator(t.begin());
540  return true;
541 }
542 
543 
544 // if <wtype_name> indicates the object <p, size> to be an STL container,
545 // and given the size of its value_type (retrieved via debug information),
546 // return number of elements and an iterator (any data it needs is stored in
547 // it_mem, which must hold DEBUG_STL_MAX_ITERATOR_SIZE bytes).
548 // returns 0 on success or an StlContainerError.
549 Status debug_stl_get_container_info(const wchar_t* type_name, const u8* p, size_t size,
550  size_t el_size, size_t* el_count, DebugStlIterator* el_iterator, void* it_mem)
551 {
552 #if MSC_VERSION >= 1400
553  UNUSED2(type_name);
554  UNUSED2(p);
555  UNUSED2(size);
556  UNUSED2(el_size);
557  UNUSED2(el_count);
558  UNUSED2(el_iterator);
559  UNUSED2(it_mem);
560  return ERR::STL_CNT_UNSUPPORTED; // NOWARN
561 #else
562 
563  bool handled = false, IsValid = false;
564 #define CONTAINER(name, type_name_pattern)\
565  else if(match_wildcard(type_name, type_name_pattern))\
566  {\
567  handled = true;\
568  IsValid = get_container_info<Any_##name>(*(Any_##name*)p, size, el_size, *el_count, *el_iterator, it_mem);\
569  }
570 #define STD_CONTAINER(name) CONTAINER(name, L"std::" WIDEN(#name) L"<*>")
571 
572  // workaround for preprocessor limitation: what we're trying to do is
573  // stringize the defined value of a macro. prepending and pasting L
574  // apparently isn't possible because macro args aren't expanded before
575  // being pasted; we therefore compare as chars[].
576 #define STRINGIZE2(id) # id
577 #define STRINGIZE(id) STRINGIZE2(id)
578 
579  if(0) {} // kickoff
580  // standard containers
581  STD_CONTAINER(deque)
582  STD_CONTAINER(list)
583  STD_CONTAINER(vector)
584 #if STL_DINKUMWARE == 405
585  STD_CONTAINER(map)
586  STD_CONTAINER(multimap)
587  STD_CONTAINER(set)
588  STD_CONTAINER(multiset)
589  STD_CONTAINER(basic_string)
590 #endif
591  // standard container adapters
592  // (note: Any_queue etc. assumes the underlying container is a deque.
593  // we make sure of that here and otherwise refuse to display it, because
594  // doing so is lots of work for little gain.)
595  CONTAINER(queue, L"std::queue<*,std::deque<*> >")
596  CONTAINER(stack, L"std::stack<*,std::deque<*> >")
597 
598  // note: do not raise warnings - these can happen for new
599  // STL classes or if the debuggee's memory is corrupted.
600  if(!handled)
601  return ERR::STL_CNT_UNKNOWN; // NOWARN
602  if(!IsValid)
603  return ERR::STL_CNT_INVALID; // NOWARN
604  return INFO::OK;
605 
606 #endif
607 }
const Status STL_CNT_UNSUPPORTED
Definition: debug_stl.h:34
#define u8
Definition: types.h:39
size_t NumElements(size_t el_size) const
Definition: debug_stl.cpp:220
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
static bool IsContainerValid(const T &t, size_t el_count)
Definition: debug_stl.cpp:492
const Status OK
Definition: status.h:386
const Status STL_CNT_INVALID
Definition: debug_stl.h:36
bool IsValid(size_t el_size) const
Definition: debug_stl.cpp:399
Definition: wnuma.cpp:46
static const u8 * DereferenceAndAdvance(typename Container::iterator &it, size_t el_size)
Definition: debug_stl.cpp:225
wchar_t * debug_stl_simplify_name(wchar_t *name)
reduce complicated STL symbol names to human-readable form.
Definition: debug_stl.cpp:80
bool get_container_info(const T &t, size_t size, size_t el_size, size_t &el_count, DebugStlIterator &el_iterator, void *it_mem)
Definition: debug_stl.cpp:519
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
Status debug_stl_get_container_info(const wchar_t *type_name, const u8 *p, size_t size, size_t el_size, size_t *el_count, DebugStlIterator *el_iterator, void *it_mem)
prepare to enumerate the elements of arbitrarily typed STL containers.
Definition: debug_stl.cpp:549
const u8 * stl_iterator(void *it_mem, size_t el_size)
Definition: debug_stl.cpp:482
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
LIB_API int debug_IsPointerBogus(const void *p)
check if a pointer appears to be totally invalid.
Definition: udbg.cpp:114
static const StatusDefinition debugStlStatusDefinitions[]
Definition: debug_stl.cpp:38
#define CONTAINER(name, type_name_pattern)
#define STRIP_NESTED(what)
Definition: debug_stl.cpp:61
i64 Status
Error handling system.
Definition: status.h:171
#define STD_CONTAINER(name)
#define T(string_literal)
Definition: secure_crt.cpp:70
#define REPLACE(what, with)
Definition: debug_stl.cpp:49
#define STATUS_ADD_DEFINITIONS(definitions)
add a module&#39;s array of StatusDefinition to the list.
Definition: status.h:216
const size_t DEBUG_STL_MAX_ITERATOR_SIZE
no STL iterator is larger than this; see below.
Definition: debug_stl.h:63
bool IsValid(size_t el_size) const
Definition: debug_stl.cpp:215
const u8 *(* DebugStlIterator)(void *internal, size_t el_size)
abstraction of all STL iterators used by debug_stl.
Definition: debug_stl.h:58
const Status STL_CNT_UNKNOWN
Definition: debug_stl.h:33
#define STRIP(what)
Definition: debug_stl.cpp:56