Add vbit-test-sec.vgtest and vbit-test-sec.stderr.exp to EXTRA_DIST.
[valgrind.git] / coregrind / m_errormgr.c
blob5dc17252004fa7d952cdcae0df6ce37b56aca49e
2 /*--------------------------------------------------------------------*/
3 /*--- Management of error messages. m_errormgr.c ---*/
4 /*--------------------------------------------------------------------*/
6 /*
7 This file is part of Valgrind, a dynamic binary instrumentation
8 framework.
10 Copyright (C) 2000-2017 Julian Seward
11 jseward@acm.org
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation; either version 2 of the
16 License, or (at your option) any later version.
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26 02111-1307, USA.
28 The GNU General Public License is contained in the file COPYING.
31 #include "pub_core_basics.h"
32 #include "pub_core_vki.h"
33 #include "pub_core_threadstate.h" // For VG_N_THREADS
34 #include "pub_core_debuginfo.h"
35 #include "pub_core_debuglog.h"
36 #include "pub_core_errormgr.h"
37 #include "pub_core_execontext.h"
38 #include "pub_core_gdbserver.h"
39 #include "pub_core_libcbase.h"
40 #include "pub_core_libcassert.h"
41 #include "pub_core_libcfile.h"
42 #include "pub_core_libcprint.h"
43 #include "pub_core_libcproc.h" // For VG_(getpid)()
44 #include "pub_core_seqmatch.h"
45 #include "pub_core_mallocfree.h"
46 #include "pub_core_options.h"
47 #include "pub_core_stacktrace.h"
48 #include "pub_core_tooliface.h"
49 #include "pub_core_translate.h" // for VG_(translate)()
50 #include "pub_core_xarray.h" // VG_(xaprintf) et al
52 #define DEBUG_ERRORMGR 0 // set to 1 for heavyweight tracing
54 /*------------------------------------------------------------*/
55 /*--- Globals ---*/
56 /*------------------------------------------------------------*/
58 /* After this many different unsuppressed errors have been observed,
59 be more conservative about collecting new ones. */
60 #define M_COLLECT_ERRORS_SLOWLY_AFTER 100
62 /* After this many different unsuppressed errors have been observed,
63 stop collecting errors at all, and tell the user their program is
64 evidently a steaming pile of camel dung. */
65 #define M_COLLECT_NO_ERRORS_AFTER_SHOWN 1000
67 /* After this many total errors have been observed, stop collecting
68 errors at all. Counterpart to M_COLLECT_NO_ERRORS_AFTER_SHOWN. */
69 #define M_COLLECT_NO_ERRORS_AFTER_FOUND 10000000
71 /* The list of error contexts found, both suppressed and unsuppressed.
72 Initially empty, and grows as errors are detected. */
73 static Error* errors = NULL;
75 /* The list of suppression directives, as read from the specified
76 suppressions file. Note that the list gets rearranged as a result
77 of the searches done by is_suppressible_error(). */
78 static Supp* suppressions = NULL;
80 /* Running count of unsuppressed errors detected. */
81 static UInt n_errs_found = 0;
83 /* Running count of suppressed errors detected. */
84 static UInt n_errs_suppressed = 0;
86 /* Running count of errors shown. */
87 static UInt n_errs_shown = 0;
89 /* Running count of unsuppressed error contexts. */
90 static UInt n_err_contexts = 0;
92 /* Running count of suppressed error contexts. */
93 static UInt n_supp_contexts = 0;
96 /* forwards ... */
97 static Supp* is_suppressible_error ( const Error* err );
99 static ThreadId last_tid_printed = 1;
101 /* Stats: number of searches of the error list initiated. */
102 static UWord em_errlist_searches = 0;
104 /* Stats: number of comparisons done during error list
105 searching. */
106 static UWord em_errlist_cmps = 0;
108 /* Stats: number of searches of the suppression list initiated. */
109 static UWord em_supplist_searches = 0;
111 /* Stats: number of comparisons done during suppression list
112 searching. */
113 static UWord em_supplist_cmps = 0;
115 /*------------------------------------------------------------*/
116 /*--- Error type ---*/
117 /*------------------------------------------------------------*/
119 /* Errors. Extensible (via the 'extra' field). Tools can use a normal
120 enum (with element values in the normal range (0..)) for 'ekind'.
121 Functions for getting/setting the tool-relevant fields are in
122 include/pub_tool_errormgr.h.
124 When errors are found and recorded with VG_(maybe_record_error)(), all
125 the tool must do is pass in the four parameters; core will
126 allocate/initialise the error record.
128 struct _Error {
129 struct _Error* next;
130 // Unique tag. This gives the error a unique identity (handle) by
131 // which it can be referred to afterwords. Currently only used for
132 // XML printing.
133 UInt unique;
134 // NULL if unsuppressed; or ptr to suppression record.
135 Supp* supp;
136 Int count;
138 // The tool-specific part
139 ThreadId tid; // Initialised by core
140 ExeContext* where; // Initialised by core
141 ErrorKind ekind; // Used by ALL. Must be in the range (0..)
142 Addr addr; // Used frequently
143 const HChar* string; // Used frequently
144 void* extra; // For any tool-specific extras
148 ExeContext* VG_(get_error_where) ( const Error* err )
150 return err->where;
153 ErrorKind VG_(get_error_kind) ( const Error* err )
155 return err->ekind;
158 Addr VG_(get_error_address) ( const Error* err )
160 return err->addr;
163 const HChar* VG_(get_error_string) ( const Error* err )
165 return err->string;
168 void* VG_(get_error_extra) ( const Error* err )
170 return err->extra;
173 UInt VG_(get_n_errs_found)( void )
175 return n_errs_found;
178 UInt VG_(get_n_errs_shown)( void )
180 return n_errs_shown;
183 /*------------------------------------------------------------*/
184 /*--- Suppression type ---*/
185 /*------------------------------------------------------------*/
187 /* Note: it is imperative this doesn't overlap with (0..) at all, as tools
188 * effectively extend it by defining their own enums in the (0..) range. */
189 typedef
190 enum {
191 // Nb: thread errors are a relic of the time when Valgrind's core
192 // could detect them. This example is left commented-out as an
193 // example should new core errors ever be added.
194 ThreadSupp = -1, /* Matches ThreadErr */
196 CoreSuppKind;
198 /* Max number of callers for context in a suppression is
199 VG_DEEPEST_BACKTRACE. */
201 /* For each caller specified for a suppression, record the nature of
202 the caller name. Not of interest to tools. */
203 typedef
204 enum {
205 NoName, /* Error case */
206 ObjName, /* Name is of an shared object file. */
207 FunName, /* Name is of a function. */
208 DotDotDot, /* Frame-level wildcard */
209 SrcName /* Name is of a src file. */
211 SuppLocTy;
213 typedef
214 struct {
215 SuppLocTy ty;
216 Bool name_is_simple_str; /* True if name is a string without
217 '?' and '*' wildcard characters. */
218 HChar* name; /* NULL for NoName and DotDotDot */
219 UInt lineno; /* Valid for SrcName. */
221 SuppLoc;
223 /* Suppressions. Tools can get/set tool-relevant parts with functions
224 declared in include/pub_tool_errormgr.h. Extensible via the 'extra' field.
225 Tools can use a normal enum (with element values in the normal range
226 (0..)) for 'skind'. */
227 struct _Supp {
228 struct _Supp* next;
229 Int count; // The number of times this error has been suppressed.
230 HChar* sname; // The name by which the suppression is referred to.
232 // Index in VG_(clo_suppressions) giving filename from which suppression
233 // was read, and the lineno in this file where sname was read.
234 Int clo_suppressions_i;
235 Int sname_lineno;
237 // Length of 'callers'
238 Int n_callers;
239 // Array of callers, for matching stack traces. First one (name of fn
240 // where err occurs) is mandatory; rest are optional.
241 SuppLoc* callers;
243 /* The tool-specific part */
244 SuppKind skind; // What kind of suppression. Must use the range (0..).
245 HChar* string; // String -- use is optional. NULL by default.
246 void* extra; // Anything else -- use is optional. NULL by default.
249 SuppKind VG_(get_supp_kind) ( const Supp* su )
251 return su->skind;
254 HChar* VG_(get_supp_string) ( const Supp* su )
256 return su->string;
259 void* VG_(get_supp_extra) ( const Supp* su )
261 return su->extra;
265 void VG_(set_supp_kind) ( Supp* su, SuppKind skind )
267 su->skind = skind;
270 void VG_(set_supp_string) ( Supp* su, HChar* string )
272 su->string = string;
275 void VG_(set_supp_extra) ( Supp* su, void* extra )
277 su->extra = extra;
281 /*------------------------------------------------------------*/
282 /*--- Helper fns ---*/
283 /*------------------------------------------------------------*/
285 // Only show core errors if the tool wants to, we're not running with -q,
286 // and were not outputting XML.
287 Bool VG_(showing_core_errors)(void)
289 return VG_(needs).core_errors && VG_(clo_verbosity) >= 1 && !VG_(clo_xml);
292 /* Compare errors, to detect duplicates.
294 static Bool eq_Error ( VgRes res, const Error* e1, const Error* e2 )
296 if (e1->ekind != e2->ekind)
297 return False;
298 if (!VG_(eq_ExeContext)(res, e1->where, e2->where))
299 return False;
301 switch (e1->ekind) {
302 //(example code, see comment on CoreSuppKind above)
303 //case ThreadErr:
304 // vg_assert(VG_(needs).core_errors);
305 // return <something>
306 default:
307 if (VG_(needs).tool_errors) {
308 return VG_TDICT_CALL(tool_eq_Error, res, e1, e2);
309 } else {
310 VG_(printf)("\nUnhandled error type: %u. VG_(needs).tool_errors\n"
311 "probably needs to be set.\n",
312 (UInt)e1->ekind);
313 VG_(core_panic)("unhandled error type");
319 /* Helper functions for suppression generation: print a single line of
320 a suppression pseudo-stack-trace, either in XML or text mode. It's
321 important that the behaviour of these two functions exactly
322 corresponds.
324 #define ERRTXT_LEN 4096
326 static void printSuppForIp_XML(UInt n, DiEpoch ep, Addr ip, void* uu_opaque)
328 const HChar *buf;
329 InlIPCursor* iipc = VG_(new_IIPC)(ep, ip);
330 do {
331 if ( VG_(get_fnname_no_cxx_demangle) (ep, ip, &buf, iipc) ) {
332 VG_(printf_xml)(" <sframe> <fun>%pS</fun> </sframe>\n", buf);
333 } else
334 if ( VG_(get_objname)(ep, ip, &buf) ) {
335 VG_(printf_xml)(" <sframe> <obj>%pS</obj> </sframe>\n", buf);
336 } else {
337 VG_(printf_xml)(" <sframe> <obj>*</obj> </sframe>\n");
339 } while (VG_(next_IIPC)(iipc));
340 VG_(delete_IIPC)(iipc);
343 static void printSuppForIp_nonXML(UInt n, DiEpoch ep, Addr ip, void* textV)
345 const HChar *buf;
346 XArray* /* of HChar */ text = (XArray*)textV;
347 InlIPCursor* iipc = VG_(new_IIPC)(ep, ip);
348 do {
349 if ( VG_(get_fnname_no_cxx_demangle) (ep, ip, &buf, iipc) ) {
350 VG_(xaprintf)(text, " fun:%s\n", buf);
351 } else
352 if ( VG_(get_objname)(ep, ip, &buf) ) {
353 VG_(xaprintf)(text, " obj:%s\n", buf);
354 } else {
355 VG_(xaprintf)(text, " obj:*\n");
357 } while (VG_(next_IIPC)(iipc));
358 VG_(delete_IIPC)(iipc);
361 /* Generate a suppression for an error, either in text or XML mode.
363 static void gen_suppression(const Error* err)
365 const HChar* name;
366 ExeContext* ec;
367 XArray* /* HChar */ text;
369 const HChar* dummy_name = "insert_a_suppression_name_here";
371 vg_assert(err);
373 ec = VG_(get_error_where)(err);
374 vg_assert(ec);
376 name = VG_TDICT_CALL(tool_get_error_name, err);
377 if (NULL == name) {
378 VG_(umsg)("(%s does not allow error to be suppressed)\n",
379 VG_(details).name);
380 return;
383 /* In XML mode, we also need to print the plain text version of the
384 suppresion in a CDATA section. What that really means is, we
385 need to generate the plaintext version both in XML and text
386 mode. So generate it into TEXT. */
387 text = VG_(newXA)( VG_(malloc), "errormgr.gen_suppression.1",
388 VG_(free), sizeof(HChar) );
390 /* Ok. Generate the plain text version into TEXT. */
391 VG_(xaprintf)(text, "{\n");
392 VG_(xaprintf)(text, " <%s>\n", dummy_name);
393 VG_(xaprintf)(text, " %s:%s\n", VG_(details).name, name);
395 HChar *xtra = NULL;
396 SizeT xtra_size = 0;
397 SizeT num_written;
399 do {
400 xtra_size += 256;
401 xtra = VG_(realloc)("errormgr.gen_suppression.2", xtra,xtra_size);
402 num_written = VG_TDICT_CALL(tool_get_extra_suppression_info,
403 err, xtra, xtra_size);
404 } while (num_written == xtra_size); // resize buffer and retry
406 // Ensure buffer is properly terminated
407 vg_assert(xtra[num_written] == '\0');
409 if (num_written)
410 VG_(xaprintf)(text, " %s\n", xtra);
412 // Print stack trace elements
413 UInt n_ips = VG_(get_ExeContext_n_ips)(ec);
414 vg_assert(n_ips > 0);
415 vg_assert(n_ips <= VG_DEEPEST_BACKTRACE);
416 VG_(apply_StackTrace)(printSuppForIp_nonXML,
417 text, VG_(get_ExeContext_epoch)(ec),
418 VG_(get_ExeContext_StackTrace)(ec),
419 n_ips);
421 VG_(xaprintf)(text, "}\n");
422 // zero terminate
423 VG_(xaprintf)(text, "%c", (HChar)0 );
424 // VG_(printf) of text
426 /* And now display it. */
427 if (! VG_(clo_xml) ) {
429 // the simple case
430 VG_(printf)("%s", (HChar*) VG_(indexXA)(text, 0) );
432 } else {
434 /* Now we have to print the XML directly. No need to go to the
435 effort of stuffing it in an XArray, since we won't need it
436 again. */
437 VG_(printf_xml)(" <suppression>\n");
438 VG_(printf_xml)(" <sname>%s</sname>\n", dummy_name);
439 VG_(printf_xml)(
440 " <skind>%pS:%pS</skind>\n", VG_(details).name, name);
441 if (num_written)
442 VG_(printf_xml)(" <skaux>%pS</skaux>\n", xtra);
444 // Print stack trace elements
445 VG_(apply_StackTrace)(printSuppForIp_XML,
446 NULL, VG_(get_ExeContext_epoch)(ec),
447 VG_(get_ExeContext_StackTrace)(ec),
448 VG_(get_ExeContext_n_ips)(ec));
450 // And now the cdata bit
451 // XXX FIXME! properly handle the case where the raw text
452 // itself contains "]]>", as specified in Protocol 4.
453 VG_(printf_xml)(" <rawtext>\n");
454 VG_(printf_xml)("<![CDATA[\n");
455 VG_(printf_xml)("%s", (HChar*) VG_(indexXA)(text, 0) );
456 VG_(printf_xml)("]]>\n");
457 VG_(printf_xml)(" </rawtext>\n");
458 VG_(printf_xml)(" </suppression>\n");
462 VG_(deleteXA)(text);
463 VG_(free)(xtra);
467 /* Figure out if we want to perform a given action for this error,
468 possibly by asking the user.
470 Bool VG_(is_action_requested) ( const HChar* action, Bool* clo )
472 HChar ch, ch2;
473 Int res;
475 /* First off, we shouldn't be asking the user anything if
476 we're in XML mode. */
477 if (VG_(clo_xml))
478 return False; /* That's a Nein, oder Nay as they say down here in B-W */
480 if (*clo == False)
481 return False;
483 VG_(umsg)("\n");
485 again:
486 VG_(printf)(
487 "==%d== "
488 "---- %s ? --- [Return/N/n/Y/y/C/c] ---- ",
489 VG_(getpid)(), action
492 res = VG_(read)(VG_(clo_input_fd), &ch, 1);
493 if (res != 1) goto ioerror;
494 /* res == 1 */
495 if (ch == '\n') return False;
496 if (ch != 'N' && ch != 'n' && ch != 'Y' && ch != 'y'
497 && ch != 'C' && ch != 'c') goto again;
499 res = VG_(read)(VG_(clo_input_fd), &ch2, 1);
500 if (res != 1) goto ioerror;
501 if (ch2 != '\n') goto again;
503 /* No, don't want to do action. */
504 if (ch == 'n' || ch == 'N') return False;
505 /* Yes, want to do action. */
506 if (ch == 'y' || ch == 'Y') return True;
507 /* No, don't want to do action, and don't ask again either. */
508 vg_assert(ch == 'c' || ch == 'C');
510 ioerror:
511 *clo = False;
512 return False;
516 /* Do actions on error, that is, immediately after an error is printed.
517 These are:
518 * possibly, call the GDB server
519 * possibly, generate a suppression.
521 static
522 void do_actions_on_error(const Error* err, Bool allow_db_attach)
524 Bool still_noisy = True;
526 /* if user wants to debug from a certain error nr, then wait for gdb/vgdb */
527 if (VG_(clo_vgdb) != Vg_VgdbNo
528 && allow_db_attach
529 && VG_(dyn_vgdb_error) <= n_errs_shown) {
530 VG_(umsg)("(action on error) vgdb me ... \n");
531 VG_(gdbserver)( err->tid );
532 VG_(umsg)("Continuing ...\n");
535 /* Or maybe we want to generate the error's suppression? */
536 if (VG_(clo_gen_suppressions) == 2
537 || (VG_(clo_gen_suppressions) == 1
538 && VG_(is_action_requested)( "Print suppression", &still_noisy ))
540 gen_suppression(err);
542 if (VG_(clo_gen_suppressions) == 1 && !still_noisy)
543 VG_(clo_gen_suppressions) = 0;
545 if (VG_(clo_exit_on_first_error)) {
546 if (VG_(clo_xml))
547 VG_(printf_xml)("</valgrindoutput>\n");
548 VG_(umsg)("\n");
549 VG_(umsg)("Exit program on first error (--exit-on-first-error=yes)\n");
550 VG_(client_exit)( VG_(clo_error_exitcode) );
555 /* Prints an error. Not entirely simple because of the differences
556 between XML and text mode output.
558 In XML mode:
560 * calls the tool's pre-show method, so the tool can create any
561 preamble ahead of the message, if it wants.
563 * prints the opening tag, and the <unique> and <tid> fields
565 * prints the tool-specific parts of the message
567 * if suppression generation is required, a suppression
569 * the closing tag
571 In text mode:
573 * calls the tool's pre-show method, so the tool can create any
574 preamble ahead of the message, if it wants.
576 * prints the tool-specific parts of the message
578 In both modes:
580 * calls do_actions_on_error. This optionally does a gdbserver call
581 and optionally prints a suppression; both of these may require user input.
583 static void pp_Error ( const Error* err, Bool allow_db_attach, Bool xml )
585 /* If this fails, you probably specified your tool's method
586 dictionary incorrectly. */
587 vg_assert(VG_(needs).tool_errors);
589 if (xml) {
591 /* Ensure that suppression generation is either completely
592 enabled or completely disabled; either way, we won't require
593 any user input. m_main.process_cmd_line_options should
594 ensure the asserted condition holds. */
595 vg_assert( VG_(clo_gen_suppressions) == 0 /* disabled */
596 || VG_(clo_gen_suppressions) == 2 /* for all errors */ );
598 /* Pre-show it to the tool */
599 VG_TDICT_CALL( tool_before_pp_Error, err );
601 /* standard preamble */
602 VG_(printf_xml)("<error>\n");
603 VG_(printf_xml)(" <unique>0x%x</unique>\n", err->unique);
604 VG_(printf_xml)(" <tid>%u</tid>\n", err->tid);
605 ThreadState* tst = VG_(get_ThreadState)(err->tid);
606 if (tst->thread_name) {
607 VG_(printf_xml)(" <threadname>%s</threadname>\n", tst->thread_name);
610 /* actually print it */
611 VG_TDICT_CALL( tool_pp_Error, err );
613 if (VG_(clo_gen_suppressions) > 0)
614 gen_suppression(err);
616 /* postamble */
617 VG_(printf_xml)("</error>\n");
618 VG_(printf_xml)("\n");
620 } else {
622 if (VG_(clo_error_markers)[0])
623 VG_(umsg)("%s\n", VG_(clo_error_markers)[0]);
624 VG_TDICT_CALL( tool_before_pp_Error, err );
626 if (VG_(tdict).tool_show_ThreadIDs_for_errors
627 && err->tid > 0 && err->tid != last_tid_printed) {
628 ThreadState* tst = VG_(get_ThreadState)(err->tid);
629 if (tst->thread_name) {
630 VG_(umsg)("Thread %u %s:\n", err->tid, tst->thread_name );
631 } else {
632 VG_(umsg)("Thread %u:\n", err->tid );
634 last_tid_printed = err->tid;
637 VG_TDICT_CALL( tool_pp_Error, err );
638 VG_(umsg)("\n");
639 if (VG_(clo_error_markers)[1])
640 VG_(umsg)("%s\n", VG_(clo_error_markers)[1]);
644 do_actions_on_error(err, allow_db_attach);
648 /* Construct an error */
649 static
650 void construct_error ( Error* err, ThreadId tid, ErrorKind ekind, Addr a,
651 const HChar* s, void* extra, ExeContext* where )
653 /* DO NOT MAKE unique_counter NON-STATIC */
654 static UInt unique_counter = 0;
656 vg_assert(tid < VG_N_THREADS);
658 /* Core-only parts */
659 err->unique = unique_counter++;
660 err->next = NULL;
661 err->supp = NULL;
662 err->count = 1;
663 err->tid = tid;
664 if (NULL == where)
665 err->where = VG_(record_ExeContext)( tid, 0 );
666 else
667 err->where = where;
669 /* Tool-relevant parts */
670 err->ekind = ekind;
671 err->addr = a;
672 err->extra = extra;
673 err->string = s;
675 /* sanity... */
676 vg_assert( tid < VG_N_THREADS );
681 /* Top-level entry point to the error management subsystem.
682 All detected errors are notified here; this routine decides if/when the
683 user should see the error. */
684 void VG_(maybe_record_error) ( ThreadId tid,
685 ErrorKind ekind, Addr a,
686 const HChar* s, void* extra )
688 Error err;
689 Error* p;
690 Error* p_prev;
691 UInt extra_size;
692 VgRes exe_res = Vg_MedRes;
693 static Bool stopping_message = False;
694 static Bool slowdown_message = False;
696 /* After M_COLLECT_NO_ERRORS_AFTER_SHOWN different errors have
697 been found, or M_COLLECT_NO_ERRORS_AFTER_FOUND total errors
698 have been found, just refuse to collect any more. This stops
699 the burden of the error-management system becoming excessive in
700 extremely buggy programs, although it does make it pretty
701 pointless to continue the Valgrind run after this point. */
702 if (VG_(clo_error_limit)
703 && (n_errs_shown >= M_COLLECT_NO_ERRORS_AFTER_SHOWN
704 || n_errs_found >= M_COLLECT_NO_ERRORS_AFTER_FOUND)
705 && !VG_(clo_xml)) {
706 if (!stopping_message) {
707 VG_(umsg)("\n");
709 if (n_errs_shown >= M_COLLECT_NO_ERRORS_AFTER_SHOWN) {
710 VG_(umsg)(
711 "More than %d different errors detected. "
712 "I'm not reporting any more.\n",
713 M_COLLECT_NO_ERRORS_AFTER_SHOWN );
714 } else {
715 VG_(umsg)(
716 "More than %d total errors detected. "
717 "I'm not reporting any more.\n",
718 M_COLLECT_NO_ERRORS_AFTER_FOUND );
721 VG_(umsg)("Final error counts will be inaccurate. "
722 "Go fix your program!\n");
723 VG_(umsg)("Rerun with --error-limit=no to disable "
724 "this cutoff. Note\n");
725 VG_(umsg)("that errors may occur in your program without "
726 "prior warning from\n");
727 VG_(umsg)("Valgrind, because errors are no longer "
728 "being displayed.\n");
729 VG_(umsg)("\n");
730 stopping_message = True;
732 return;
735 /* Ignore it if error acquisition is disabled for this thread. */
736 { ThreadState* tst = VG_(get_ThreadState)(tid);
737 if (tst->err_disablement_level > 0)
738 return;
741 /* After M_COLLECT_ERRORS_SLOWLY_AFTER different errors have
742 been found, be much more conservative about collecting new
743 ones. */
744 if (n_errs_shown >= M_COLLECT_ERRORS_SLOWLY_AFTER
745 && !VG_(clo_xml)) {
746 exe_res = Vg_LowRes;
747 if (!slowdown_message) {
748 VG_(umsg)("\n");
749 VG_(umsg)("More than %d errors detected. Subsequent errors\n",
750 M_COLLECT_ERRORS_SLOWLY_AFTER);
751 VG_(umsg)("will still be recorded, but in less "
752 "detail than before.\n");
753 slowdown_message = True;
757 /* Build ourselves the error */
758 construct_error ( &err, tid, ekind, a, s, extra, NULL );
760 /* First, see if we've got an error record matching this one. */
761 em_errlist_searches++;
762 p = errors;
763 p_prev = NULL;
764 while (p != NULL) {
765 em_errlist_cmps++;
766 if (eq_Error(exe_res, p, &err)) {
767 /* Found it. */
768 p->count++;
769 if (p->supp != NULL) {
770 /* Deal correctly with suppressed errors. */
771 p->supp->count++;
772 n_errs_suppressed++;
773 } else {
774 n_errs_found++;
777 /* Move p to the front of the list so that future searches
778 for it are faster. It also allows to print the last
779 error (see VG_(show_last_error). */
780 if (p_prev != NULL) {
781 vg_assert(p_prev->next == p);
782 p_prev->next = p->next;
783 p->next = errors;
784 errors = p;
787 return;
789 p_prev = p;
790 p = p->next;
793 /* Didn't see it. Copy and add. */
795 /* OK, we're really going to collect it. The context is on the stack and
796 will disappear shortly, so we must copy it. First do the main
797 (non-'extra') part.
799 Then VG_(tdict).tool_update_extra can update the 'extra' part. This
800 is for when there are more details to fill in which take time to work
801 out but don't affect our earlier decision to include the error -- by
802 postponing those details until now, we avoid the extra work in the
803 case where we ignore the error. Ugly.
805 Then, if there is an 'extra' part, copy it too, using the size that
806 VG_(tdict).tool_update_extra returned. Also allow for people using
807 the void* extra field for a scalar value like an integer.
810 /* copy main part */
811 p = VG_(malloc)("errormgr.mre.1", sizeof(Error));
812 *p = err;
814 /* update 'extra' */
815 switch (ekind) {
816 //(example code, see comment on CoreSuppKind above)
817 //case ThreadErr:
818 // vg_assert(VG_(needs).core_errors);
819 // extra_size = <something>
820 // break;
821 default:
822 vg_assert(VG_(needs).tool_errors);
823 extra_size = VG_TDICT_CALL(tool_update_extra, p);
824 break;
827 /* copy the error string, if there is one.
828 note: if we would have many errors with big strings, using a
829 DedupPoolAlloc for these strings will avoid duplicating
830 such string in each error using it. */
831 if (NULL != p->string) {
832 p->string = VG_(strdup)("errormgr.mre.2", p->string);
835 /* copy block pointed to by 'extra', if there is one */
836 if (NULL != p->extra && 0 != extra_size) {
837 void* new_extra = VG_(malloc)("errormgr.mre.3", extra_size);
838 VG_(memcpy)(new_extra, p->extra, extra_size);
839 p->extra = new_extra;
842 p->next = errors;
843 p->supp = is_suppressible_error(&err);
844 errors = p;
845 if (p->supp == NULL) {
846 /* update stats */
847 n_err_contexts++;
848 n_errs_found++;
849 n_errs_shown++;
850 /* Actually show the error; more complex than you might think. */
851 pp_Error( p, /*allow_db_attach*/True, VG_(clo_xml) );
852 } else {
853 n_supp_contexts++;
854 n_errs_suppressed++;
855 p->supp->count++;
859 /* Second top-level entry point to the error management subsystem, for
860 errors that the tool wants to report immediately, eg. because they're
861 guaranteed to only happen once. This avoids all the recording and
862 comparing stuff. But they can be suppressed; returns True if it is
863 suppressed. Bool 'print_error' dictates whether to print the error.
864 Bool 'count_error' dictates whether to count the error in n_errs_found.
866 Bool VG_(unique_error) ( ThreadId tid, ErrorKind ekind, Addr a, const HChar* s,
867 void* extra, ExeContext* where, Bool print_error,
868 Bool allow_db_attach, Bool count_error )
870 Error err;
871 Supp *su;
873 /* Ignore it if error acquisition is disabled for this thread. */
874 ThreadState* tst = VG_(get_ThreadState)(tid);
875 if (tst->err_disablement_level > 0)
876 return False; /* ignored, not suppressed */
878 /* Build ourselves the error */
879 construct_error ( &err, tid, ekind, a, s, extra, where );
881 /* Unless it's suppressed, we're going to show it. Don't need to make
882 a copy, because it's only temporary anyway.
884 Then update the 'extra' part with VG_(tdict).tool_update_extra),
885 because that can have an affect on whether it's suppressed. Ignore
886 the size return value of VG_(tdict).tool_update_extra, because we're
887 not copying 'extra'. Similarly, 's' is also not copied. */
888 (void)VG_TDICT_CALL(tool_update_extra, &err);
890 su = is_suppressible_error(&err);
891 if (NULL == su) {
892 if (count_error) {
893 n_errs_found++;
894 n_err_contexts++;
897 if (print_error) {
898 /* update stats */
899 n_errs_shown++;
900 /* Actually show the error; more complex than you might think. */
901 pp_Error(&err, allow_db_attach, VG_(clo_xml));
903 return False;
905 } else {
906 if (count_error) {
907 n_errs_suppressed++;
908 n_supp_contexts++;
910 su->count++;
911 return True;
916 /*------------------------------------------------------------*/
917 /*--- Exported fns ---*/
918 /*------------------------------------------------------------*/
920 /* Show the used suppressions. Returns False if no suppression
921 got used. */
922 static Bool show_used_suppressions ( void )
924 Supp *su;
925 Bool any_supp;
927 if (VG_(clo_xml))
928 VG_(printf_xml)("<suppcounts>\n");
930 any_supp = False;
931 for (su = suppressions; su != NULL; su = su->next) {
932 if (su->count <= 0)
933 continue;
934 if (VG_(clo_xml)) {
935 VG_(printf_xml)( " <pair>\n"
936 " <count>%d</count>\n"
937 " <name>%pS</name>\n"
938 " </pair>\n",
939 su->count, su->sname );
940 } else {
941 HChar *xtra = NULL;
942 Int xtra_size = 0;
943 SizeT num_written;
944 // blank line before the first shown suppression, if any
945 if (!any_supp)
946 VG_(dmsg)("\n");
948 do {
949 xtra_size += 256;
950 xtra = VG_(realloc)("errormgr.sus.1", xtra, xtra_size);
951 num_written = VG_TDICT_CALL(tool_print_extra_suppression_use,
952 su, xtra, xtra_size);
953 } while (num_written == xtra_size); // resize buffer and retry
955 // Ensure buffer is properly terminated
956 vg_assert(xtra[num_written] == '\0');
958 HChar *filename = *(HChar**) VG_(indexXA)(VG_(clo_suppressions),
959 su->clo_suppressions_i);
960 VG_(dmsg)("used_suppression: %6d %s %s:%d%s%s\n", su->count, su->sname,
961 filename,
962 su->sname_lineno,
963 num_written ? " " : "", xtra);
964 VG_(free)(xtra);
966 any_supp = True;
969 if (VG_(clo_xml))
970 VG_(printf_xml)("</suppcounts>\n");
972 return any_supp;
975 /* Show all the errors that occurred, and possibly also the
976 suppressions used. */
977 void VG_(show_all_errors) ( Int verbosity, Bool xml )
979 Int i, n_min;
980 Error *p, *p_min;
981 Bool any_supp;
983 if (verbosity == 0)
984 return;
986 /* If we're printing XML, just show the suppressions and stop. */
987 if (xml) {
988 (void)show_used_suppressions();
989 return;
992 /* We only get here if not printing XML. */
993 VG_(umsg)("ERROR SUMMARY: "
994 "%u errors from %u contexts (suppressed: %u from %u)\n",
995 n_errs_found, n_err_contexts,
996 n_errs_suppressed, n_supp_contexts );
998 if (verbosity <= 1)
999 return;
1001 // We do the following only at -v or above, and only in non-XML
1002 // mode
1004 /* Print the contexts in order of increasing error count.
1005 Once an error is shown, we add a huge value to its count to filter it
1006 out.
1007 After having shown all errors, we reset count to the original value. */
1008 for (i = 0; i < n_err_contexts; i++) {
1009 n_min = (1 << 30) - 1;
1010 p_min = NULL;
1011 for (p = errors; p != NULL; p = p->next) {
1012 if (p->supp != NULL) continue;
1013 if (p->count < n_min) {
1014 n_min = p->count;
1015 p_min = p;
1018 // XXX: this isn't right. See bug 203651.
1019 if (p_min == NULL) continue; //VG_(core_panic)("show_all_errors()");
1021 VG_(umsg)("\n");
1022 VG_(umsg)("%d errors in context %d of %u:\n",
1023 p_min->count, i+1, n_err_contexts);
1024 pp_Error( p_min, False/*allow_db_attach*/, False /* xml */ );
1026 // We're not printing XML -- we'd have exited above if so.
1027 vg_assert(! xml);
1029 if ((i+1 == VG_(clo_dump_error))) {
1030 StackTrace ips = VG_(get_ExeContext_StackTrace)(p_min->where);
1031 VG_(translate) ( 0 /* dummy ThreadId; irrelevant due to debugging*/,
1032 ips[0], /*debugging*/True, 0xFE/*verbosity*/,
1033 /*bbs_done*/0,
1034 /*allow redir?*/True);
1037 p_min->count = p_min->count + (1 << 30);
1040 /* reset the counts, otherwise a 2nd call does not show anything anymore */
1041 for (p = errors; p != NULL; p = p->next) {
1042 if (p->count >= (1 << 30))
1043 p->count = p->count - (1 << 30);
1047 any_supp = show_used_suppressions();
1049 if (any_supp)
1050 VG_(umsg)("\n");
1051 // reprint this, so users don't have to scroll way up to find
1052 // the first printing
1053 VG_(umsg)("ERROR SUMMARY: "
1054 "%u errors from %u contexts (suppressed: %u from %u)\n",
1055 n_errs_found, n_err_contexts, n_errs_suppressed,
1056 n_supp_contexts );
1059 void VG_(show_last_error) ( void )
1061 if (n_err_contexts == 0) {
1062 VG_(umsg)("No errors yet\n");
1063 return;
1066 pp_Error( errors, False/*allow_db_attach*/, False/*xml*/ );
1070 /* Show occurrence counts of all errors, in XML form. */
1071 void VG_(show_error_counts_as_XML) ( void )
1073 Error* err;
1074 VG_(printf_xml)("<errorcounts>\n");
1075 for (err = errors; err != NULL; err = err->next) {
1076 if (err->supp != NULL)
1077 continue;
1078 if (err->count <= 0)
1079 continue;
1080 VG_(printf_xml)(" <pair>\n");
1081 VG_(printf_xml)(" <count>%d</count>\n", err->count);
1082 VG_(printf_xml)(" <unique>0x%x</unique>\n", err->unique);
1083 VG_(printf_xml)(" </pair>\n");
1085 VG_(printf_xml)("</errorcounts>\n");
1086 VG_(printf_xml)("\n");
1090 /*------------------------------------------------------------*/
1091 /*--- Suppression parsing ---*/
1092 /*------------------------------------------------------------*/
1094 /* Get the next char from fd into *out_buf. Returns 1 if success,
1095 0 if eof or < 0 if error. */
1097 static Int get_char ( Int fd, HChar* out_buf )
1099 Int r;
1100 static HChar buf[256];
1101 static Int buf_size = 0;
1102 static Int buf_used = 0;
1103 vg_assert(buf_size >= 0 && buf_size <= sizeof buf);
1104 vg_assert(buf_used >= 0 && buf_used <= buf_size);
1105 if (buf_used == buf_size) {
1106 r = VG_(read)(fd, buf, sizeof buf);
1107 if (r < 0) return r; /* read failed */
1108 vg_assert(r >= 0 && r <= sizeof buf);
1109 buf_size = r;
1110 buf_used = 0;
1112 if (buf_size == 0)
1113 return 0; /* eof */
1114 vg_assert(buf_size >= 0 && buf_size <= sizeof buf);
1115 vg_assert(buf_used >= 0 && buf_used < buf_size);
1116 *out_buf = buf[buf_used];
1117 buf_used++;
1118 return 1;
1121 // Get a non blank non comment line.
1122 // Returns True if eof.
1123 static Bool get_nbnc_line ( Int fd, HChar** bufpp, SizeT* nBufp, Int* lineno )
1125 HChar* buf = *bufpp;
1126 SizeT nBuf = *nBufp;
1127 HChar ch;
1128 Int n, i;
1130 vg_assert(lineno); // lineno needed to correctly track line numbers.
1132 while (True) {
1133 buf[0] = 0;
1134 /* First, read until a non-blank char appears. */
1135 while (True) {
1136 n = get_char(fd, &ch);
1137 if (n == 1 && !VG_(isspace)(ch)) break;
1138 if (n == 1 && ch == '\n')
1139 (*lineno)++;
1140 if (n <= 0) return True;
1143 /* Now, read the line into buf. */
1144 i = 0;
1145 buf[i++] = ch; buf[i] = 0;
1146 while (True) {
1147 n = get_char(fd, &ch);
1148 if (n <= 0) return False; /* the next call will return True */
1149 if (ch == '\n')
1150 (*lineno)++;
1151 if (ch == '\n') break;
1152 if (i > 0 && i == nBuf-1) {
1153 *nBufp = nBuf = nBuf * 2;
1154 #define RIDICULOUS 100000
1155 vg_assert2(nBuf < RIDICULOUS, // Just a sanity check, really.
1156 "VG_(get_line): line longer than %d chars, aborting\n",
1157 RIDICULOUS);
1158 *bufpp = buf = VG_(realloc)("errormgr.get_line.1", buf, nBuf);
1160 buf[i++] = ch; buf[i] = 0;
1162 while (i > 1 && VG_(isspace)(buf[i-1])) {
1163 i--; buf[i] = 0;
1166 // VG_(printf)("The line *%p %d is '%s'\n", lineno, *lineno, buf);
1167 /* Ok, we have a line. If a non-comment line, return.
1168 If a comment line, start all over again. */
1169 if (buf[0] != '#') return False;
1173 // True if buf starts with fun: or obj: or is ...
1174 static Bool is_location_line (const HChar* buf)
1176 return VG_(strncmp)(buf, "fun:", 4) == 0
1177 || VG_(strncmp)(buf, "obj:", 4) == 0
1178 || VG_(strcmp)(buf, "...") == 0;
1181 Bool VG_(get_line) ( Int fd, HChar** bufpp, SizeT* nBufp, Int* lineno )
1183 Bool eof = get_nbnc_line (fd, bufpp, nBufp, lineno);
1185 if (eof)
1186 return True;
1188 if (is_location_line(*bufpp))
1189 return True; // Not a extra suppr line
1190 else
1191 return False; // A suppression extra line
1194 /* True if s contains no wildcard (?, *) characters. */
1195 static Bool is_simple_str (const HChar *s)
1197 while (*s) {
1198 if (*s == '?' || *s == '*')
1199 return False;
1200 s++;
1202 return True;
1205 /* buf contains the raw name of a caller, supposedly either
1206 fun:some_function_name or
1207 obj:some_object_name or
1208 src:some_file_name or
1209 src:some_file_name:line# or
1211 Set p->ty and p->name accordingly.
1212 p->name is allocated and set to the string
1213 after the descriptor (fun:, obj:, or src: san line#) part.
1214 p->lineno is set to non-zero if line# specified; 0 otherwise.
1215 Returns False if failed.
1217 static Bool setLocationTy ( SuppLoc* p, const HChar *buf )
1219 if (VG_(strncmp)(buf, "fun:", 4) == 0) {
1220 p->name = VG_(strdup)("errormgr.sLTy.1", buf+4);
1221 p->name_is_simple_str = is_simple_str (p->name);
1222 p->ty = FunName;
1223 return True;
1225 if (VG_(strncmp)(buf, "obj:", 4) == 0) {
1226 p->name = VG_(strdup)("errormgr.sLTy.2", buf+4);
1227 p->name_is_simple_str = is_simple_str (p->name);
1228 p->ty = ObjName;
1229 return True;
1231 if (VG_(strncmp)(buf, "src:", 4) == 0) {
1232 p->name = VG_(strdup)("errormgr.sLTy.3", buf+4);
1233 p->name_is_simple_str = is_simple_str (p->name);
1234 p->ty = SrcName;
1235 HChar *s = VG_(strchr)(p->name, ':');
1236 if (s != NULL) {
1237 *s++ = '\0'; // trim colon
1238 p->lineno = (UInt) VG_(strtoll10)(s, NULL);
1239 } else {
1240 p->lineno = 0;
1242 return True;
1244 if (VG_(strcmp)(buf, "...") == 0) {
1245 p->name = NULL;
1246 p->name_is_simple_str = False;
1247 p->ty = DotDotDot;
1248 return True;
1250 VG_(printf)("location should be \"...\", or should start "
1251 "with \"fun:\", \"obj:\", or \"src:\"\n");
1252 return False;
1256 /* Look for "tool" in a string like "tool1,tool2,tool3" */
1257 static Bool tool_name_present(const HChar *name, const HChar *names)
1259 Bool found;
1260 HChar *s = NULL; /* Shut gcc up */
1261 Int len = VG_(strlen)(name);
1263 found = (NULL != (s = VG_(strstr)(names, name))
1264 && (s == names || *(s-1) == ',')
1265 && (*(s+len) == ',' || *(s+len) == '\0'));
1267 return found;
1270 /* Read suppressions from the file specified in
1271 VG_(clo_suppressions)[clo_suppressions_i]
1272 and place them in the suppressions list. If there's any difficulty
1273 doing this, just give up -- there's no point in trying to recover.
1275 static void load_one_suppressions_file ( Int clo_suppressions_i )
1277 const HChar* filename = *(HChar**) VG_(indexXA)(VG_(clo_suppressions),
1278 clo_suppressions_i);
1279 SysRes sres;
1280 Int fd, i, j, lineno = 0;
1281 Bool got_a_location_line_read_by_tool;
1282 Bool eof;
1283 SizeT nBuf = 200;
1284 HChar* buf = VG_(malloc)("errormgr.losf.1", nBuf);
1285 HChar* tool_names;
1286 HChar* supp_name;
1287 const HChar* err_str = NULL;
1288 SuppLoc tmp_callers[VG_DEEPEST_BACKTRACE];
1290 // Check it's not a directory.
1291 if (VG_(is_dir)( filename )) {
1292 if (VG_(clo_xml))
1293 VG_(printf_xml)("</valgrindoutput>\n");
1294 VG_(umsg)("FATAL: suppressions file \"%s\" is a directory\n", filename );
1295 VG_(exit)(1);
1298 // Open the suppression file.
1299 sres = VG_(open)( filename, VKI_O_RDONLY, 0 );
1300 if (sr_isError(sres)) {
1301 if (VG_(clo_xml))
1302 VG_(printf_xml)("</valgrindoutput>\n");
1303 VG_(umsg)("FATAL: can't open suppressions file \"%s\"\n", filename );
1304 VG_(exit)(1);
1306 fd = sr_Res(sres);
1308 # define BOMB(S) { err_str = S; goto syntax_error; }
1310 while (True) {
1311 /* Assign and initialise the two suppression halves (core and tool) */
1312 Supp* supp;
1313 supp = VG_(malloc)("errormgr.losf.1", sizeof(Supp));
1314 supp->count = 0;
1316 // Initialise temporary reading-in buffer.
1317 for (i = 0; i < VG_DEEPEST_BACKTRACE; i++) {
1318 tmp_callers[i].ty = NoName;
1319 tmp_callers[i].name_is_simple_str = False;
1320 tmp_callers[i].name = NULL;
1323 supp->string = supp->extra = NULL;
1325 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1326 if (eof) {
1327 VG_(free)(supp);
1328 break;
1331 if (!VG_STREQ(buf, "{")) BOMB("expected '{' or end-of-file");
1333 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1335 if (eof || VG_STREQ(buf, "}")) BOMB("unexpected '}'");
1337 supp->sname = VG_(strdup)("errormgr.losf.2", buf);
1338 supp->clo_suppressions_i = clo_suppressions_i;
1339 supp->sname_lineno = lineno;
1341 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1343 if (eof) BOMB("unexpected end-of-file (expecting tool:suppr)");
1345 /* Check it has the "tool1,tool2,...:supp" form (look for ':') */
1346 i = 0;
1347 while (True) {
1348 if (buf[i] == ':') break;
1349 if (buf[i] == '\0') BOMB("malformed 'tool1,tool2,...:supp' line");
1350 i++;
1352 buf[i] = '\0'; /* Replace ':', splitting into two strings */
1354 tool_names = & buf[0];
1355 supp_name = & buf[i+1];
1357 if (VG_(needs).core_errors && tool_name_present("core", tool_names)) {
1358 // A core suppression
1359 //(example code, see comment on CoreSuppKind above)
1360 //if (VG_STREQ(supp_name, "Thread"))
1361 // supp->skind = ThreadSupp;
1362 //else
1363 BOMB("unknown core suppression type");
1365 else if (VG_(needs).tool_errors
1366 && tool_name_present(VG_(details).name, tool_names)) {
1367 // A tool suppression
1368 if (VG_TDICT_CALL(tool_recognised_suppression, supp_name, supp)) {
1369 /* Do nothing, function fills in supp->skind */
1370 } else {
1371 BOMB("unknown tool suppression type");
1374 else {
1375 // Ignore rest of suppression
1376 while (True) {
1377 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1378 if (eof) BOMB("unexpected end-of-file (when skipping suppression)");
1379 if (VG_STREQ(buf, "}"))
1380 break;
1382 VG_(free)(supp->sname);
1383 VG_(free)(supp);
1384 continue;
1387 buf[0] = 0;
1388 // tool_read_extra_suppression_info might read lines
1389 // from fd till a location line.
1390 if (VG_(needs).tool_errors
1391 && !VG_TDICT_CALL(tool_read_extra_suppression_info,
1392 fd, &buf, &nBuf, &lineno, supp)) {
1393 BOMB("bad or missing extra suppression info");
1396 got_a_location_line_read_by_tool = buf[0] != 0 && is_location_line(buf);
1398 /* the main frame-descriptor reading loop */
1399 i = 0;
1400 while (True) {
1401 if (got_a_location_line_read_by_tool) {
1402 got_a_location_line_read_by_tool = False;
1403 eof = False;
1404 } else {
1405 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1407 if (eof)
1408 BOMB("unexpected end-of-file (when reading stack trace)");
1409 if (VG_STREQ(buf, "}")) {
1410 if (i > 0) {
1411 break;
1412 } else {
1413 BOMB("missing stack trace");
1416 if (i == VG_DEEPEST_BACKTRACE)
1417 BOMB("too many callers in stack trace");
1418 if (i > 0 && i >= VG_(clo_backtrace_size))
1419 break;
1420 if (!setLocationTy(&(tmp_callers[i]), buf))
1421 BOMB("location should be \"...\", or should start "
1422 "with \"fun:\", \"obj:\", or \"src:\"");
1423 i++;
1426 // If the num callers is >= VG_(clo_backtrace_size), ignore any extra
1427 // lines and grab the '}'.
1428 if (!VG_STREQ(buf, "}")) {
1429 do {
1430 eof = get_nbnc_line ( fd, &buf, &nBuf, &lineno );
1431 } while (!eof && !VG_STREQ(buf, "}"));
1434 // Reject entries which are entirely composed of frame
1435 // level wildcards.
1436 vg_assert(i > 0); // guaranteed by frame-descriptor reading loop
1437 for (j = 0; j < i; j++) {
1438 if (tmp_callers[j].ty == FunName || tmp_callers[j].ty == ObjName
1439 || tmp_callers[j].ty == SrcName) {
1440 break;
1442 vg_assert(tmp_callers[j].ty == DotDotDot);
1444 vg_assert(j >= 0 && j <= i);
1445 if (j == i) {
1446 // we didn't find any non-"..." entries
1447 BOMB("suppression must contain at least one location "
1448 "line which is not \"...\"");
1451 // Copy tmp_callers[] into supp->callers[]
1452 supp->n_callers = i;
1453 supp->callers = VG_(malloc)("errormgr.losf.4", i * sizeof(SuppLoc));
1454 for (i = 0; i < supp->n_callers; i++) {
1455 supp->callers[i] = tmp_callers[i];
1458 supp->next = suppressions;
1459 suppressions = supp;
1461 VG_(free)(buf);
1462 VG_(close)(fd);
1463 return;
1465 syntax_error:
1466 if (VG_(clo_xml))
1467 VG_(printf_xml)("</valgrindoutput>\n");
1468 VG_(umsg)("FATAL: in suppressions file \"%s\" near line %d:\n",
1469 filename, lineno );
1470 VG_(umsg)(" %s\n", err_str );
1472 VG_(close)(fd);
1473 VG_(umsg)("exiting now.\n");
1474 VG_(exit)(1);
1476 # undef BOMB
1480 void VG_(load_suppressions) ( void )
1482 Int i;
1483 suppressions = NULL;
1484 for (i = 0; i < VG_(sizeXA)(VG_(clo_suppressions)); i++) {
1485 if (VG_(clo_verbosity) > 1) {
1486 VG_(dmsg)("Reading suppressions file: %s\n",
1487 *(HChar**) VG_(indexXA)(VG_(clo_suppressions), i));
1489 load_one_suppressions_file( i );
1494 /*------------------------------------------------------------*/
1495 /*--- Matching errors to suppressions ---*/
1496 /*------------------------------------------------------------*/
1498 /* Parameterising functions for the use of VG_(generic_match) in
1499 suppression-vs-error matching. The suppression frames (SuppLoc)
1500 play the role of 'pattern'-element, and the error frames (IPs,
1501 hence simply Addrs) play the role of 'input'. In short then, we're
1502 matching a sequence of Addrs against a pattern composed of a
1503 sequence of SuppLocs.
1505 static Bool supploc_IsStar ( const void* supplocV )
1507 const SuppLoc* supploc = supplocV;
1508 return supploc->ty == DotDotDot;
1511 static Bool supploc_IsQuery ( const void* supplocV )
1513 return False; /* there's no '?' equivalent in the supp syntax */
1516 /* IPtoFunOrObjCompleter is a lazy completer of the IPs
1517 needed to match an error with the suppression patterns.
1518 The matching between an IP and a suppression pattern is done either
1519 with the IP function name or with the IP object name.
1520 First time the fun or obj name is needed for an IP member
1521 of a stack trace, it will be computed and stored in names.
1522 Also, if the IP corresponds to one or more inlined function calls,
1523 the inlined function names are expanded.
1524 The IPtoFunOrObjCompleter type is designed to minimise the nr of
1525 allocations and the nr of debuginfo search. */
1526 typedef
1527 struct {
1528 DiEpoch epoch; // used to interpret .ips
1529 StackTrace ips; // stack trace we are lazily completing.
1530 UWord n_ips; // nr of elements in ips.
1532 // VG_(generic_match) calls haveInputInpC to check
1533 // for the presence of an input element identified by ixInput
1534 // (i.e. a number that identifies the ixInput element of the
1535 // input sequence). It calls supp_pattEQinp to match this input
1536 // element with a pattern.
1537 // When inlining info is used to provide inlined function calls
1538 // in stacktraces, one IP in ips can be expanded in several
1539 // function names. So, each time input (or presence of input)
1540 // is requested by VG_(generic_match), we will expand
1541 // more IP of ips till we have expanded enough to reach the
1542 // input element requested (or we cannot expand anymore).
1544 UWord n_ips_expanded;
1545 // n_ips_expanded maintains the nr of elements in ips that we have
1546 // already expanded.
1547 UWord n_expanded;
1548 // n_expanded maintains the nr of elements resulting from the expansion
1549 // of the n_ips_expanded IPs. Without inlined function calls,
1550 // n_expanded == n_ips_expanded. With inlining info,
1551 // n_expanded >= n_ips_expanded.
1553 Int* n_offsets_per_ip;
1554 // n_offsets_per_ip[i] gives the nr of offsets in fun_offsets and
1555 // obj_offsets resulting of the expansion of ips[i].
1556 // The sum of all n_expanded_per_ip must be equal to n_expanded.
1557 // This array allows to retrieve the position in ips corresponding to
1558 // an ixInput.
1560 // size (in elements) of fun_offsets and obj_offsets.
1561 // (fun|obj)_offsets are reallocated if more space is needed
1562 // to expand an IP.
1563 UWord sz_offsets;
1565 Int* fun_offsets;
1566 // fun_offsets[ixInput] is the offset in names where the
1567 // function name for the ixInput element of the input sequence
1568 // can be found. As one IP of ips can be expanded in several
1569 // function calls due to inlined function calls, we can have more
1570 // elements in fun_offsets than in ips.
1571 // An offset -1 means the function name has not yet been computed.
1572 Int* obj_offsets;
1573 // Similarly, obj_offsets[ixInput] gives the offset for the
1574 // object name for ips[ixInput]
1575 // (-1 meaning object name not yet been computed).
1577 // All function names and object names will be concatenated
1578 // in names. names is reallocated on demand.
1579 HChar *names;
1580 Int names_szB; // size of names.
1581 Int names_free; // offset first free HChar in names.
1583 IPtoFunOrObjCompleter;
1585 static void pp_ip2fo (const IPtoFunOrObjCompleter* ip2fo)
1587 Int i, j;
1588 Int o;
1590 VG_(printf)("n_ips %lu n_ips_expanded %lu resulting in n_expanded %lu\n",
1591 ip2fo->n_ips, ip2fo->n_ips_expanded, ip2fo->n_expanded);
1592 for (i = 0; i < ip2fo->n_ips_expanded; i++) {
1593 o = 0;
1594 for (j = 0; j < i; j++)
1595 o += ip2fo->n_offsets_per_ip[j];
1596 VG_(printf)("ips %d 0x08%lx offset [%d,%d] ",
1597 i, ip2fo->ips[i],
1598 o, o+ip2fo->n_offsets_per_ip[i]-1);
1599 for (j = 0; j < ip2fo->n_offsets_per_ip[i]; j++) {
1600 VG_(printf)("%sfun:%s obj:%s\n",
1601 j == 0 ? "" : " ",
1602 ip2fo->fun_offsets[o+j] == -1 ?
1603 "<not expanded>" : &ip2fo->names[ip2fo->fun_offsets[o+j]],
1604 ip2fo->obj_offsets[o+j] == -1 ?
1605 "<not expanded>" : &ip2fo->names[ip2fo->obj_offsets[o+j]]);
1610 /* free the memory in ip2fo.
1611 At debuglog 4, su (or NULL) will be used to show the matching
1612 (or non matching) with ip2fo. */
1613 static void clearIPtoFunOrObjCompleter ( const Supp *su,
1614 IPtoFunOrObjCompleter* ip2fo)
1616 if (DEBUG_ERRORMGR || VG_(debugLog_getLevel)() >= 4) {
1617 if (su) {
1618 HChar *filename = *(HChar**) VG_(indexXA)(VG_(clo_suppressions),
1619 su->clo_suppressions_i);
1620 VG_(dmsg)("errormgr matching end suppression %s %s:%d matched:\n",
1621 su->sname,
1622 filename,
1623 su->sname_lineno);
1624 } else {
1625 VG_(dmsg)("errormgr matching end no suppression matched:\n");
1627 VG_(pp_StackTrace) (ip2fo->epoch, ip2fo->ips, ip2fo->n_ips);
1628 pp_ip2fo(ip2fo);
1630 if (ip2fo->n_offsets_per_ip) VG_(free)(ip2fo->n_offsets_per_ip);
1631 if (ip2fo->fun_offsets) VG_(free)(ip2fo->fun_offsets);
1632 if (ip2fo->obj_offsets) VG_(free)(ip2fo->obj_offsets);
1633 if (ip2fo->names) VG_(free)(ip2fo->names);
1636 /* Grow ip2fo->names to ensure we have NEEDED characters available
1637 in ip2fo->names and returns a pointer to the first free char. */
1638 static HChar* grow_names(IPtoFunOrObjCompleter* ip2fo, SizeT needed)
1640 if (ip2fo->names_szB
1641 < ip2fo->names_free + needed) {
1642 if (needed < ERRTXT_LEN) needed = ERRTXT_LEN;
1644 ip2fo->names
1645 = VG_(realloc)("foc_names",
1646 ip2fo->names,
1647 ip2fo->names_szB + needed);
1648 ip2fo->names_szB += needed;
1650 return ip2fo->names + ip2fo->names_free;
1653 /* foComplete returns the function name or object name for ixInput.
1654 If needFun, returns the function name for this input
1655 else returns the object name for this input.
1656 The function name or object name will be computed and added in
1657 names if not yet done. */
1658 static HChar* foComplete(IPtoFunOrObjCompleter* ip2fo,
1659 Int ixInput, Bool needFun)
1661 vg_assert (ixInput < ip2fo->n_expanded);
1662 vg_assert (VG_(clo_read_inline_info) || ixInput < ip2fo->n_ips);
1664 // ptr to the offset array for function offsets (if needFun)
1665 // or object offsets (if !needFun).
1666 Int** offsets;
1667 if (needFun)
1668 offsets = &ip2fo->fun_offsets;
1669 else
1670 offsets = &ip2fo->obj_offsets;
1672 // Complete Fun name or Obj name for IP if not yet done.
1673 if ((*offsets)[ixInput] == -1) {
1674 const HChar* caller;
1676 (*offsets)[ixInput] = ip2fo->names_free;
1677 if (DEBUG_ERRORMGR) VG_(printf)("marking %s ixInput %d offset %d\n",
1678 needFun ? "fun" : "obj",
1679 ixInput, ip2fo->names_free);
1680 if (needFun) {
1681 // With inline info, fn names must have been completed already.
1682 vg_assert (!VG_(clo_read_inline_info));
1683 /* Get the function name into 'caller_name', or "???"
1684 if unknown. */
1685 // Nb: C++-mangled names are used in suppressions. Do, though,
1686 // Z-demangle them, since otherwise it's possible to wind
1687 // up comparing "malloc" in the suppression against
1688 // "_vgrZU_libcZdsoZa_malloc" in the backtrace, and the
1689 // two of them need to be made to match.
1690 if (!VG_(get_fnname_no_cxx_demangle)(ip2fo->epoch,
1691 ip2fo->ips[ixInput],
1692 &caller,
1693 NULL))
1694 caller = "???";
1695 } else {
1696 /* Get the object name into 'caller_name', or "???"
1697 if unknown. */
1698 UWord i;
1699 UWord last_expand_pos_ips = 0;
1700 UWord pos_ips;
1702 /* First get the pos in ips corresponding to ixInput */
1703 for (pos_ips = 0; pos_ips < ip2fo->n_expanded; pos_ips++) {
1704 last_expand_pos_ips += ip2fo->n_offsets_per_ip[pos_ips];
1705 if (ixInput < last_expand_pos_ips)
1706 break;
1708 /* pos_ips is the position in ips corresponding to ixInput.
1709 last_expand_pos_ips is the last offset in fun/obj where
1710 ips[pos_ips] has been expanded. */
1712 if (!VG_(get_objname)(ip2fo->epoch, ip2fo->ips[pos_ips], &caller))
1713 caller = "???";
1715 // Have all inlined calls pointing at this object name
1716 for (i = last_expand_pos_ips - ip2fo->n_offsets_per_ip[pos_ips] + 1;
1717 i < last_expand_pos_ips;
1718 i++) {
1719 ip2fo->obj_offsets[i] = ip2fo->names_free;
1720 if (DEBUG_ERRORMGR)
1721 VG_(printf) (" set obj_offset %lu to %d\n",
1722 i, ip2fo->names_free);
1725 SizeT caller_len = VG_(strlen)(caller);
1726 HChar* caller_name = grow_names(ip2fo, caller_len + 1);
1727 VG_(strcpy)(caller_name, caller);
1728 ip2fo->names_free += caller_len + 1;
1729 if (DEBUG_ERRORMGR) pp_ip2fo(ip2fo);
1732 return ip2fo->names + (*offsets)[ixInput];
1735 // Grow fun and obj _offsets arrays to have at least n_req elements.
1736 // Ensure n_offsets_per_ip is allocated.
1737 static void grow_offsets(IPtoFunOrObjCompleter* ip2fo, Int n_req)
1739 Int i;
1741 // n_offsets_per_ip must always have the size of the ips array
1742 if (ip2fo->n_offsets_per_ip == NULL) {
1743 ip2fo->n_offsets_per_ip = VG_(malloc)("grow_offsets",
1744 ip2fo->n_ips * sizeof(Int));
1745 for (i = 0; i < ip2fo->n_ips; i++)
1746 ip2fo->n_offsets_per_ip[i] = 0;
1749 if (ip2fo->sz_offsets >= n_req)
1750 return;
1752 // Avoid too much re-allocation by allocating at least ip2fo->n_ips
1753 // elements and at least a few more elements than the current size.
1754 if (n_req < ip2fo->n_ips)
1755 n_req = ip2fo->n_ips;
1756 if (n_req < ip2fo->sz_offsets + 5)
1757 n_req = ip2fo->sz_offsets + 5;
1759 ip2fo->fun_offsets = VG_(realloc)("grow_offsets", ip2fo->fun_offsets,
1760 n_req * sizeof(Int));
1761 for (i = ip2fo->sz_offsets; i < n_req; i++)
1762 ip2fo->fun_offsets[i] = -1;
1764 ip2fo->obj_offsets = VG_(realloc)("grow_offsets", ip2fo->obj_offsets,
1765 n_req * sizeof(Int));
1766 for (i = ip2fo->sz_offsets; i < n_req; i++)
1767 ip2fo->obj_offsets[i] = -1;
1769 ip2fo->sz_offsets = n_req;
1772 // Expands more IPs from ip2fo->ips.
1773 static void expandInput (IPtoFunOrObjCompleter* ip2fo, UWord ixInput )
1775 while (ip2fo->n_ips_expanded < ip2fo->n_ips
1776 && ip2fo->n_expanded <= ixInput) {
1777 if (VG_(clo_read_inline_info)) {
1778 // Expand one more IP in one or more calls.
1779 const Addr IP = ip2fo->ips[ip2fo->n_ips_expanded];
1780 InlIPCursor *iipc;
1782 iipc = VG_(new_IIPC)(ip2fo->epoch, IP);
1783 // The only thing we really need is the nr of inlined fn calls
1784 // corresponding to the IP we will expand.
1785 // However, computing this is mostly the same as finding
1786 // the function name. So, let's directly complete the function name.
1787 do {
1788 const HChar *caller;
1789 grow_offsets(ip2fo, ip2fo->n_expanded+1);
1790 ip2fo->fun_offsets[ip2fo->n_expanded] = ip2fo->names_free;
1791 if (!VG_(get_fnname_no_cxx_demangle)(ip2fo->epoch, IP,
1792 &caller,
1793 iipc))
1794 caller = "???";
1795 SizeT caller_len = VG_(strlen)(caller);
1796 HChar* caller_name = grow_names(ip2fo, caller_len + 1);
1797 VG_(strcpy)(caller_name, caller);
1798 ip2fo->names_free += caller_len + 1;
1799 ip2fo->n_expanded++;
1800 ip2fo->n_offsets_per_ip[ip2fo->n_ips_expanded]++;
1801 } while (VG_(next_IIPC)(iipc));
1802 ip2fo->n_ips_expanded++;
1803 VG_(delete_IIPC) (iipc);
1804 } else {
1805 // Without inlined fn call info, expansion simply
1806 // consists in allocating enough elements in (fun|obj)_offsets.
1807 // The function or object names themselves will be completed
1808 // when requested.
1809 Int i;
1810 grow_offsets(ip2fo, ip2fo->n_ips);
1811 ip2fo->n_ips_expanded = ip2fo->n_ips;
1812 ip2fo->n_expanded = ip2fo->n_ips;
1813 for (i = 0; i < ip2fo->n_ips; i++)
1814 ip2fo->n_offsets_per_ip[i] = 1;
1819 static Bool haveInputInpC (void* inputCompleterV, UWord ixInput )
1821 IPtoFunOrObjCompleter* ip2fo = (IPtoFunOrObjCompleter*)inputCompleterV;
1822 expandInput(ip2fo, ixInput);
1823 return ixInput < ip2fo->n_expanded;
1826 static Bool supp_pattEQinp ( const void* supplocV, const void* addrV,
1827 void* inputCompleterV, UWord ixInput )
1829 const SuppLoc* supploc = (const SuppLoc*)supplocV; /* PATTERN */
1830 IPtoFunOrObjCompleter* ip2fo = (IPtoFunOrObjCompleter*)inputCompleterV;
1831 const HChar* funobjsrc_name; // Fun, Obj, or src file name.
1832 UInt src_lineno = 0;
1833 Bool ret;
1835 expandInput(ip2fo, ixInput);
1836 vg_assert(ixInput < ip2fo->n_expanded);
1838 /* So, does this IP address match this suppression-line? */
1839 switch (supploc->ty) {
1840 case DotDotDot:
1841 /* supp_pattEQinp is a callback from VG_(generic_match). As
1842 per the spec thereof (see include/pub_tool_seqmatch.h), we
1843 should never get called with a pattern value for which the
1844 _IsStar or _IsQuery function would return True. Hence
1845 this can't happen. */
1846 vg_assert(0);
1847 case ObjName:
1848 funobjsrc_name = foComplete(ip2fo, ixInput, False /*needFun*/);
1849 break;
1850 case FunName:
1851 funobjsrc_name = foComplete(ip2fo, ixInput, True /*needFun*/);
1852 break;
1853 case SrcName: {
1854 const HChar* src_dirname; // placeholder only
1855 ret = VG_(get_filename_linenum)(VG_(current_DiEpoch)(),
1856 ip2fo->ips[ixInput], &funobjsrc_name, &src_dirname, &src_lineno);
1857 if (!ret) {
1858 /* No file name found for location so no way this is a match. */
1859 return ret;
1861 break;
1863 default:
1864 vg_assert(0);
1867 /* So now we have the function or object name in funobjsrc_name, and
1868 the pattern (at the character level) to match against is in
1869 supploc->name. Hence (and leading to a re-entrant call of
1870 VG_(generic_match) if there is a wildcard character): */
1871 if (supploc->name_is_simple_str)
1872 ret = VG_(strcmp) (supploc->name, funobjsrc_name) == 0;
1873 else
1874 ret = VG_(string_match)(supploc->name, funobjsrc_name);
1875 if (ret && supploc->ty == SrcName && supploc->lineno != 0) {
1876 ret = (supploc->lineno == src_lineno);
1878 if (DEBUG_ERRORMGR)
1879 VG_(printf) ("supp_pattEQinp %s patt %s ixInput %lu value:%s (lineno:%u vs %u) match:%s\n",
1880 supploc->ty == FunName ? "fun" : (supploc->ty == SrcName ? "src" : "obj"),
1881 supploc->name, ixInput, funobjsrc_name,
1882 supploc->ty == SrcName ? supploc->lineno : 0,
1883 supploc->ty == SrcName ? src_lineno : 0,
1884 ret ? "yes" : "no");
1885 return ret;
1888 /////////////////////////////////////////////////////
1890 static Bool supp_matches_callers(IPtoFunOrObjCompleter* ip2fo,
1891 const Supp* su)
1893 /* Unwrap the args and set up the correct parameterisation of
1894 VG_(generic_match), using supploc_IsStar, supploc_IsQuery and
1895 supp_pattEQinp. */
1896 /* note, StackTrace ip2fo->ips === Addr* */
1897 SuppLoc* supps = su->callers;
1898 UWord n_supps = su->n_callers;
1899 UWord szbPatt = sizeof(SuppLoc);
1900 Bool matchAll = False; /* we just want to match a prefix */
1901 if (DEBUG_ERRORMGR) {
1902 HChar *filename = *(HChar**) VG_(indexXA)(VG_(clo_suppressions),
1903 su->clo_suppressions_i);
1904 VG_(dmsg)(" errormgr Checking match with %s %s:%d\n",
1905 su->sname,
1906 filename,
1907 su->sname_lineno);
1909 return
1910 VG_(generic_match)(
1911 matchAll,
1912 /*PATT*/supps, szbPatt, n_supps, 0/*initial ixPatt*/,
1913 /*INPUT*/
1914 NULL, 0, 0, /* input/szbInput/nInput 0, as using an inputCompleter */
1915 0/*initial ixInput*/,
1916 supploc_IsStar, supploc_IsQuery, supp_pattEQinp,
1917 ip2fo, haveInputInpC
1921 /////////////////////////////////////////////////////
1923 static
1924 Bool supp_matches_error(const Supp* su, const Error* err)
1926 switch (su->skind) {
1927 //(example code, see comment on CoreSuppKind above)
1928 //case ThreadSupp:
1929 // return (err->ekind == ThreadErr);
1930 default:
1931 if (VG_(needs).tool_errors) {
1932 return VG_TDICT_CALL(tool_error_matches_suppression, err, su);
1933 } else {
1934 VG_(printf)(
1935 "\nUnhandled suppression type: %u. VG_(needs).tool_errors\n"
1936 "probably needs to be set.\n",
1937 (UInt)err->ekind);
1938 VG_(core_panic)("unhandled suppression type");
1943 /////////////////////////////////////////////////////
1945 /* Does an error context match a suppression? ie is this a suppressible
1946 error? If so, return a pointer to the Supp record, otherwise NULL.
1947 Tries to minimise the number of symbol searches since they are expensive.
1949 static Supp* is_suppressible_error ( const Error* err )
1951 Supp* su;
1952 Supp* su_prev;
1954 IPtoFunOrObjCompleter ip2fo;
1955 /* Conceptually, ip2fo contains an array of function names and an array of
1956 object names, corresponding to the array of IP of err->where.
1957 These names are just computed 'on demand' (so once maximum),
1958 then stored (efficiently, avoiding too many allocs) in ip2fo to be
1959 re-usable for the matching of the same IP with the next suppression
1960 pattern.
1962 VG_(generic_match) gets this 'IP to Fun or Obj name completer' as one
1963 of its arguments. It will then pass it to the function
1964 supp_pattEQinp which will then lazily complete the IP function name or
1965 object name inside ip2fo. Next time the fun or obj name for the same
1966 IP is needed (i.e. for the matching with the next suppr pattern), then
1967 the fun or obj name will not be searched again in the debug info. */
1969 /* stats gathering */
1970 em_supplist_searches++;
1972 /* Prepare the lazy input completer. */
1973 ip2fo.epoch = VG_(get_ExeContext_epoch)(err->where);
1974 ip2fo.ips = VG_(get_ExeContext_StackTrace)(err->where);
1975 ip2fo.n_ips = VG_(get_ExeContext_n_ips)(err->where);
1976 ip2fo.n_ips_expanded = 0;
1977 ip2fo.n_expanded = 0;
1978 ip2fo.sz_offsets = 0;
1979 ip2fo.n_offsets_per_ip = NULL;
1980 ip2fo.fun_offsets = NULL;
1981 ip2fo.obj_offsets = NULL;
1982 ip2fo.names = NULL;
1983 ip2fo.names_szB = 0;
1984 ip2fo.names_free = 0;
1986 /* See if the error context matches any suppression. */
1987 if (DEBUG_ERRORMGR || VG_(debugLog_getLevel)() >= 4)
1988 VG_(dmsg)("errormgr matching begin\n");
1989 su_prev = NULL;
1990 for (su = suppressions; su != NULL; su = su->next) {
1991 em_supplist_cmps++;
1992 if (supp_matches_error(su, err)
1993 && supp_matches_callers(&ip2fo, su)) {
1994 /* got a match. */
1995 /* Inform the tool that err is suppressed by su. */
1996 (void)VG_TDICT_CALL(tool_update_extra_suppression_use, err, su);
1997 /* Move this entry to the head of the list
1998 in the hope of making future searches cheaper. */
1999 if (su_prev) {
2000 vg_assert(su_prev->next == su);
2001 su_prev->next = su->next;
2002 su->next = suppressions;
2003 suppressions = su;
2005 clearIPtoFunOrObjCompleter(su, &ip2fo);
2006 return su;
2008 su_prev = su;
2010 clearIPtoFunOrObjCompleter(NULL, &ip2fo);
2011 return NULL; /* no matches */
2014 /* Show accumulated error-list and suppression-list search stats.
2016 void VG_(print_errormgr_stats) ( void )
2018 VG_(dmsg)(
2019 " errormgr: %'lu supplist searches, %'lu comparisons during search\n",
2020 em_supplist_searches, em_supplist_cmps
2022 VG_(dmsg)(
2023 " errormgr: %'lu errlist searches, %'lu comparisons during search\n",
2024 em_errlist_searches, em_errlist_cmps
2028 /*--------------------------------------------------------------------*/
2029 /*--- end ---*/
2030 /*--------------------------------------------------------------------*/