]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
This commit completely overhauls the leak checker. In particular:
authorNicholas Nethercote <njn@valgrind.org>
Mon, 9 Mar 2009 22:52:24 +0000 (22:52 +0000)
committerNicholas Nethercote <njn@valgrind.org>
Mon, 9 Mar 2009 22:52:24 +0000 (22:52 +0000)
- It heavily refactors the code:  uses better names for things, splits up
  complex functions that behaved very differently depending on how they were
  called, removes some redundancies, and generally makes it much simpler and
  easier to follow.

- It adds lots of comments, both inline, and also a big explanatory one at
  the top which makes it clear exactly how the leak checker works and also
  exactly what is meant by definite, possible, and indirect leaks.  It also
  has some ideas for future improvements.

- All tabs have been converted to spaces.

It also improves the functionality:

- Previously if you did --leak-check=summary, indirect and suppressed
  blocks were counted as definite leaks.  Now they are done properly, and so
  the summary results from --leak-check=summary match those from
  --leak-check=yes.

- Previously, some possibly reachable blocks were miscategorised as
  definitely reachable, because only the pointer to the block itself was
  considered, not any preceding pointers in the chain.  This is now fixed.

- Added memcheck/tests/leak-cases, which fully tests all the possible
  combinations of directly/indirectly reachable and possibly/definitely
  reachable.

And it improves the manual quite a bit, and the FAQ a little bit.

This doesn't fix the leak checker to handle MALLOCLIKE_BLOCK works that have
been taken from within malloc'd blocks, but I think I know how to do it and
hope to do so in a subsequent commit.

It also changes all instances of "<constant>memcheck</constant>" in the
Memcheck manual to "Memcheck", for consistency and because "Memcheck" is
easier to write.  There's one similar case for DRD but I didn't change that.

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@9330

15 files changed:
NEWS
docs/xml/FAQ.xml
memcheck/docs/mc-manual.xml
memcheck/mc_errors.c
memcheck/mc_include.h
memcheck/mc_leakcheck.c
memcheck/mc_main.c
memcheck/memcheck.h
memcheck/tests/Makefile.am
memcheck/tests/filter_stderr
memcheck/tests/leak-cases-full.stderr.exp [new file with mode: 0644]
memcheck/tests/leak-cases-full.vgtest [new file with mode: 0644]
memcheck/tests/leak-cases-summary.stderr.exp [new file with mode: 0644]
memcheck/tests/leak-cases-summary.vgtest [new file with mode: 0644]
memcheck/tests/leak-cases.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 1388af2a53d2139694c9bb63583c8733308eb431..f016250a4b39dc18e6a13ea83c37db6b2b16b192 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,15 @@ Release 3.5.0 (???)
   [XXX: consider adding VALGRIND_COUNT_LEAK_BYTES as a synonym and
   deprecating VALGRIND_COUNT_LEAKS, which wasn't a good name to begin with]
 
+* Memcheck's leak checker has been improved.  
+  - The results for --leak-check=summary now match the summary results for
+    --leak-check=full.  Previously they could differ because
+    --leak-check=summary counted "indirectly lost" blocks and "suppressed"
+    blocks as "definitely lost".
+  - Blocks that are only reachable via at least one interior-pointer, but
+    are directly pointed to by a start-pointer, were previously marked as
+    "still reachable".  They are now correctly marked as "possibly lost".
+
 * The location of some install files has changed.  This should not affect
   most users.  Those who might be affected:
 
index 109ea100fd3708aa60e344fc70a4d066079ed950..be1ddad74029421ec5fd1d750ad94368e0bf82e9 100644 (file)
@@ -500,21 +500,28 @@ int main(void)
 <qandaentry id="faq.deflost">
   <question id="q-deflost">
     <para>With Memcheck's memory leak detector, what's the
-    difference between "definitely lost", "possibly lost", "still
-    reachable", and "suppressed"?</para>
+    difference between "definitely lost", "indirectly lost", "possibly
+    lost", "still reachable", and "suppressed"?</para>
   </question>
   <answer id="a-deflost">
-    <para>The details are in the Memcheck section of the user
-    manual.</para>
+    <para>The details are in the Memcheck section of the user manual.</para>
 
     <para>In short:</para>
     <itemizedlist>
       <listitem>
         <para>"definitely lost" means your program is leaking memory --
-        fix it!</para>
+        fix those leaks!</para>
       </listitem>
       <listitem>
-        <para>"possibly lost" means your program is probably leaking
+        <para>"indirectly lost" means your program is leaking memory in
+        a pointer-based structure.  (E.g. if the root node of a binary tree
+        is "definitely lost", all the children will be "indirectly lost".) 
+        If you fix the "definitely lost" leaks, the "indirectly lost" leaks
+        should go away.
+        </para>
+      </listitem>
+      <listitem>
+        <para>"possibly lost" means your program is leaking
         memory, unless you're doing funny things with pointers.</para>
       </listitem>
       <listitem>
index 1d9b806828316538c68f0cdf37e0b1369267ae07..be4a761363540aa6ac6b1d8a6c04aaf95c564a0a 100644 (file)
@@ -65,11 +65,11 @@ the following problems:</para>
       <option><![CDATA[--undef-value-errors=<yes|no> [default: yes] ]]></option>
     </term>
     <listitem>
-      <para>Controls whether <constant>memcheck</constant> reports
+      <para>Controls whether Memcheck reports
       uses of undefined value errors.  Set this to
       <varname>no</varname> if you don't want to see undefined value
       errors.  It also has the side effect of speeding up
-      <constant>memcheck</constant> somewhat.
+      memcheck somewhat.
       </para>
     </listitem>
   </varlistentry>
@@ -79,7 +79,7 @@ the following problems:</para>
       <option><![CDATA[--track-origins=<yes|no> [default: no] ]]></option>
     </term>
       <listitem>
-        <para>Controls whether <constant>memcheck</constant> tracks
+        <para>Controls whether Memcheck tracks
         the origin of uninitialised values.  By default, it does not,
         which means that although it can tell you that an
         uninitialised value is being used in a dangerous way, it
@@ -87,43 +87,42 @@ the following problems:</para>
         often makes it difficult to track down the root problem.
         </para>
         <para>When set
-        to <varname>yes</varname>, <constant>memcheck</constant> keeps
+        to <varname>yes</varname>, Memcheck keeps
         track of the origins of all uninitialised values.  Then, when
         an uninitialised value error is
-        reported, <constant>memcheck</constant> will try to show the
+        reported, Memcheck will try to show the
         origin of the value.  An origin can be one of the following
         four places: a heap block, a stack allocation, a client
         request, or miscellaneous other sources (eg, a call
         to <varname>brk</varname>).
         </para>
         <para>For uninitialised values originating from a heap
-        block, <constant>memcheck</constant> shows where the block was
+        block, Memcheck shows where the block was
         allocated.  For uninitialised values originating from a stack
-        allocation, <constant>memcheck</constant> can tell you which
+        allocation, Memcheck can tell you which
         function allocated the value, but no more than that -- typically
         it shows you the source location of the opening brace of the
         function.  So you should carefully check that all of the
         function's local variables are initialised properly.
         </para>
         <para>Performance overhead: origin tracking is expensive.  It
-        halves <constant>memcheck</constant>'s speed and increases
+        halves Memcheck's speed and increases
         memory use by a minimum of 100MB, and possibly more.
         Nevertheless it can drastically reduce the effort required to
         identify the root cause of uninitialised value errors, and so
         is often a programmer productivity win, despite running
         more slowly.
         </para>
-        <para>Accuracy: <constant>memcheck</constant> tracks origins
+        <para>Accuracy: Memcheck tracks origins
         quite accurately.  To avoid very large space and time
         overheads, some approximations are made.  It is possible,
-        although unlikely, that
-        <constant>memcheck</constant> will report an incorrect origin,
-        or not be able to identify any origin.
+        although unlikely, that Memcheck will report an incorrect origin, or
+        not be able to identify any origin.
         </para>
         <para>Note that the combination
         <option>--track-origins=yes</option>
         and <option>--undef-value-errors=no</option> is
-        nonsensical.  <constant>memcheck</constant> checks for and
+        nonsensical.  Memcheck checks for and
         rejects this combination at startup.
         </para>
         <para>Origin tracking is a new feature, introduced in Valgrind
@@ -138,12 +137,9 @@ the following problems:</para>
     </term>
     <listitem>
       <para>When enabled, search for memory leaks when the client
-      program finishes.  A memory leak means a malloc'd block, which has
-      not yet been free'd, but to which no pointer can be found.  Such a
-      block can never be free'd by the program, since no pointer to it
-      exists.  If set to <varname>summary</varname>, it says how many
-      leaks occurred.  If set to <varname>full</varname> or
-      <varname>yes</varname>, it gives details of each individual
+      program finishes.  If set to <varname>summary</varname>, it says how
+      many leaks occurred.  If set to <varname>full</varname> or
+      <varname>yes</varname>, it also gives details of each individual
       leak.</para>
     </listitem>
   </varlistentry>
@@ -153,17 +149,12 @@ the following problems:</para>
       <option><![CDATA[--show-reachable=<yes|no> [default: no] ]]></option>
     </term>
     <listitem>
-      <para>When disabled, the memory leak detector only shows blocks
-      for which it cannot find a pointer to at all, or it can only find
-      a pointer to the middle of.  These blocks are prime candidates for
-      memory leaks.  When enabled, the leak detector also reports on
-      blocks which it could find a pointer to.  Your program could, at
-      least in principle, have freed such blocks before exit.  Contrast
-      this to blocks for which no pointer, or only an interior pointer
-      could be found: they are more likely to indicate memory leaks,
-      because you do not actually have a pointer to the start of the
-      block which you can hand to <function>free</function>, even if you
-      wanted to.</para>
+      <para>When disabled, the memory leak detector only shows "definitely
+      lost" and "possibly lost" blocks.  When enabled, the leak detector also
+      shows "reachable" and "indirectly lost" blocks.  (In other words, it
+      shows all blocks, except suppressed ones, so
+      <computeroutput>--show-all</computeroutput> would be a better name for
+      it.)
     </listitem>
   </varlistentry>
 
@@ -173,7 +164,7 @@ the following problems:</para>
     </term>
     <listitem>
       <para>When doing leak checking, determines how willing
-      <constant>memcheck</constant> is to consider different backtraces to
+      Memcheck is to consider different backtraces to
       be the same.  When set to <varname>low</varname>, only the first
       two entries need match.  When <varname>med</varname>, four entries
       have to match.  When <varname>high</varname>, all entries need to
@@ -183,11 +174,11 @@ the following problems:</para>
       <option>--leak-resolution=high</option> together with
       <option>--num-callers=40</option> or some such large number.  Note
       however that this can give an overwhelming amount of information,
-      which is why the defaults are 4 callers and low-resolution
-      matching.</para>
+      which is why the defaults give less information.
+      </para>
 
       <para>Note that the <option>--leak-resolution=</option> setting
-      does not affect <constant>memcheck's</constant> ability to find
+      does not affect Memcheck's ability to find
       leaks.  It only changes how the results are presented.</para>
     </listitem>
   </varlistentry>
@@ -204,14 +195,14 @@ the following problems:</para>
       and placed in a queue of freed blocks.  The purpose is to defer as
       long as possible the point at which freed-up memory comes back
       into circulation.  This increases the chance that
-      <constant>memcheck</constant> will be able to detect invalid
+      Memcheck will be able to detect invalid
       accesses to blocks for some significant period of time after they
       have been freed.</para>
 
       <para>This flag specifies the maximum total size, in bytes, of the
       blocks in the queue.  The default value is ten million bytes.
       Increasing this increases the total amount of memory used by
-      <constant>memcheck</constant> but may detect invalid uses of freed
+      Memcheck but may detect invalid uses of freed
       blocks which would otherwise go undetected.</para>
     </listitem>
   </varlistentry>
@@ -245,7 +236,7 @@ the following problems:</para>
       <option><![CDATA[--partial-loads-ok=<yes|no> [default: no] ]]></option>
     </term>
     <listitem>
-      <para>Controls how <constant>memcheck</constant> handles word-sized,
+      <para>Controls how Memcheck handles word-sized,
       word-aligned loads from addresses for which some bytes are
       addressable and others are not.  When <varname>yes</varname>, such
       loads do not produce an address error.  Instead, loaded bytes
@@ -632,45 +623,126 @@ which blocks have not been freed.
 </para>
 
 <para>If <option>--leak-check</option> is set appropriately, for each
-remaining block, Memcheck scans the entire address space of the process,
-looking for pointers to the block.  Each block fits into one of the
-three following categories.</para>
+remaining block, Memcheck determines if the block is reachable from pointers
+within the root-set.  The root-set consists of (a) general purpose registers
+of all threads, and (b) initialised, aligned, pointer-sized data words in
+accessible client memory, including stacks.</para>
+
+<para>There are two ways a block can be reached.  The first is with a
+"start-pointer", i.e. a pointer to the start of the block.  The second is
+with an "interior-pointer", i.e. a pointer to the middle of the block.  The
+pointer might have originally been a start-pointer and have been moved
+along, or it might be entirely unrelated, just a coincidence.  It's unclear
+whether such a pointer should be considered as genuinely pointing to the
+block.</para>
+
+<para>With that in mind, consider the nine possible cases described by the
+following figure.</para>
+
+<programlisting><![CDATA[
+     Pointer chain            AAA Category    BBB Category
+     -------------            ------------    ------------
+(1)  RRR ------------> BBB                    DR
+(2)  RRR ---> AAA ---> BBB    DR              IR
+(3)  RRR               BBB                    DL
+(4)  RRR      AAA ---> BBB    DL              IL
+(5)  RRR ------?-----> BBB                    (y)DR, (n)DL
+(6)  RRR ---> AAA -?-> BBB    DR              (y)IR, (n)DL
+(7)  RRR -?-> AAA ---> BBB    (y)DR, (n)DL    (y)IR, (n)IL
+(8)  RRR -?-> AAA -?-> BBB    (y)DR, (n)DL    (y,y)IR, (n,y)IL, (_,n)DL
+(9)  RRR      AAA -?-> BBB    DL              (y)IL, (n)DL
+
+Pointer chain legend:
+- RRR: a root set node or DR block
+- AAA, BBB: heap blocks
+- --->: a start-pointer
+- -?->: an interior-pointer
+
+Category legend:
+- DR: Directly reachable
+- IR: Indirectly reachable
+- DL: Directly lost
+- IL: Indirectly lost
+- (y)XY: it's XY if the interior-pointer is a real pointer
+- (n)XY: it's XY if the interior-pointer is not a real pointer
+- (_)XY: it's XY in either case
+]]></programlisting>
+
+<para>Every possible case can be reduced to one of the above nine.  Memcheck
+merges some of these cases in its output, resulting in the following four
+categories.</para>
+
 
 <itemizedlist>
 
   <listitem>
-    <para>Still reachable: A pointer to the start of the block is found.
-    This usually indicates programming sloppiness.  Since the block is
-    still pointed at, the programmer could, at least in principle, free
-    it before program exit.  Because these are very common and arguably
-    not a problem, Memcheck won't report such blocks unless
-    <option>--show-reachable=yes</option> is specified.</para>
+    <para>"Still reachable". This covers cases 1 and 2 (for the BBB blocks)
+    above.  A start-pointer or chain of start-pointers to the block is
+    found.  Since the block is still pointed at, the programmer could, at
+    least in principle, have freed it before program exit.  Because these
+    are very common and arguably not a problem, Memcheck won't report such
+    blocks individually unless <option>--show-reachable=yes</option> is
+    specified.</para>
   </listitem>
 
   <listitem>
-    <para>Possibly lost, or "dubious": A pointer to the interior of the
-    block is found.  The pointer might originally have pointed to the
-    start and have been moved along, or it might be entirely unrelated.
-    Memcheck deems such a block as "dubious", because it's unclear
-    whether or not a pointer to it still exists.</para>
+    <para>"Definitely lost".  This covers case 3 (for the BBB blocks) above.
+    This means that no pointer to the block can be found.  The block is
+    classified as "lost", because the programmer could not possibly have
+    freed it at program exit, since no pointer to it exists.  This is likely
+    a symptom of having lost the pointer at some earlier point in the
+    program.  Such cases should be fixed by the programmer.</para>
+    </listitem>
+
+  <listitem>
+    <para>"Indirectly lost".  This covers cases 4 and 9 (for the BBB blocks)
+    above.  This means that the block is lost, not because there are no
+    pointers to it, but rather because all the blocks that point to it are
+    themselves lost.  For example, if you have a binary tree and the root
+    node is lost, all its children nodes will be indirectly lost.  Because
+    the problem will disappear if the definitely lost block that caused the
+    indirect leak is fixed, Memcheck won't report such blocks individually
+    unless <option>--show-reachable=yes</option> is specified.</para>
   </listitem>
 
   <listitem>
-    <para>Definitely lost, or "leaked": The worst outcome is that no
-    pointer to the block can be found.  The block is classified as
-    "leaked", because the programmer could not possibly have freed it at
-    program exit, since no pointer to it exists.  This is likely a
-    symptom of having lost the pointer at some earlier point in the
-    program.</para>
-    </listitem>
+    <para>"Possibly lost".  This covers cases 5--8 (for the BBB blocks)
+    above.  This means that a chain of one or more pointers to the block has
+    been found, but at least one of the pointers is an interior-pointer.
+    This could just be a random value in memory that happens to point into a
+    block, and so you shouldn't consider this ok unless you know you have
+    interior-pointers.</para>
+  </listitem>
 
 </itemizedlist>
 
-<para>For each block mentioned, Memcheck will also tell you where the
-block was allocated.  It cannot tell you how or why the pointer to a
-leaked block has been lost; you have to work that out for yourself.  In
-general, you should attempt to ensure your programs do not have any
-leaked or dubious blocks at exit.</para>
+<para>(Note: This mapping of the nine possible cases onto four categories is
+not necessarily the best way that leaks could be reported;  in particular,
+interior-pointers are treated inconsistently.  It is possible the
+categorisation may be improved in the future.)</para>
+
+<para>Furthermore, if suppressions exists for a block, it will be reported
+as "suppressed" no matter what which of the above four categories it belongs
+to.</para>
+
+
+<para>The following is an example leak summary.</para>
+
+<programlisting><![CDATA[
+LEAK SUMMARY:
+   definitely lost: 48 bytes in 3 blocks.
+   indirectly lost: 32 bytes in 2 blocks.
+     possibly lost: 96 bytes in 6 blocks.
+   still reachable: 64 bytes in 4 blocks.
+        suppressed: 0 bytes in 0 blocks.
+]]></programlisting>
+
+<para>If <computeroutput>--leak-check=full</computeroutput> is specified,
+Memcheck will give details for each definitely lost or possibly lost block,
+including where it was allocated.  It cannot tell you when or how or why the
+pointer to a leaked block was lost; you have to work that out for yourself.
+In general, you should attempt to ensure your programs do not have any
+definitely lost or possibly lost blocks at exit.</para>
 
 <para>For example:</para>
 <programlisting><![CDATA[
@@ -679,23 +751,32 @@ leaked or dubious blocks at exit.</para>
    by 0x........: mk (leak-tree.c:11)
    by 0x........: main (leak-tree.c:39)
 
-88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost 
-                           in loss record 13 of 14
+88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 14
    at 0x........: malloc (vg_replace_malloc.c:...)
    by 0x........: mk (leak-tree.c:11)
    by 0x........: main (leak-tree.c:25)
 ]]></programlisting>
 
 <para>The first message describes a simple case of a single 8 byte block
-that has been definitely lost.  The second case mentions both "direct"
-and "indirect" leaks.  The distinction is that a direct leak is a block
-which has no pointers to it.  An indirect leak is a block which is only
-pointed to by other leaked blocks.  Both kinds of leak are bad.</para>
-
-<para>The precise area of memory in which Memcheck searches for pointers
-is: all naturally-aligned machine-word-sized words found in memory
-that Memcheck's records indicate is both accessible and initialised.
-</para>
+that has been definitely lost.  The second case mentions another 8 byte
+block that has been definitely lost;  the difference is that a further 80
+bytes in other blocks are indirectly lost because of this lost block.</para>
+
+<para>If you specify <computeroutput>--show-reachable=yes</computeroutput>,
+reachable and indirectly lost blocks will also be shown, as the following
+two examples show.</para>
+
+<programlisting><![CDATA[
+64 bytes in 4 blocks are still reachable in loss record 2 of 4
+   at 0x........: malloc (vg_replace_malloc.c:177)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:74)
+
+32 bytes in 2 blocks are indirectly lost in loss record 1 of 4
+   at 0x........: malloc (vg_replace_malloc.c:177)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:80)
+]]></programlisting>
 
 </sect2>
 
@@ -1159,22 +1240,30 @@ arguments.</para>
     <para><varname>VALGRIND_CHECK_VALUE_IS_DEFINED</varname>: a quick and easy
     way to find out whether Valgrind thinks a particular value
     (lvalue, to be precise) is addressable and defined.  Prints an error
-    message if not.  Returns no value.</para>
+    message if not.  It has no return value.</para>
+  </listitem>
+
+  <listitem>
+    <para><varname>VALGRIND_DO_LEAK_CHECK</varname>: does a full memory leak
+    check (like <computeroutput>--leak-check=full</computeroutput> right now.
+    This is useful for incrementally checking for leaks between arbitrary
+    places in the program's execution.  It has no return value.</para>
   </listitem>
 
   <listitem>
-    <para><varname>VALGRIND_DO_LEAK_CHECK</varname>: runs the memory
-    leak detector right now.  Is useful for incrementally checking for
-    leaks between arbitrary places in the program's execution.  Returns
-    no value.</para>
+    <para><varname>VALGRIND_DO_QUICK_LEAK_CHECK</varname>: like
+    <varname>VALGRIND_DO_LEAK_CHECK</varname>, except it produces only a leak
+    summary (like <computeroutput>--leak-check=summary</computeroutput>).
+    It has no return value.</para>
   </listitem>
 
   <listitem>
     <para><varname>VALGRIND_COUNT_LEAKS</varname>: fills in the four
     arguments with the number of bytes of memory found by the previous
-    leak check to be leaked, dubious, reachable and suppressed.  Again,
-    useful in test harness code, after calling
-    <varname>VALGRIND_DO_LEAK_CHECK</varname>.</para>
+    leak check to be leaked (i.e. the sum of direct leaks and indirect leaks),
+    dubious, reachable and suppressed.  Again, useful in test harness code,
+    after calling <varname>VALGRIND_DO_LEAK_CHECK</varname> or
+    <varname>VALGRIND_DO_QUICK_LEAK_CHECK</varname>.</para>
   </listitem>
 
   <listitem>
index e7fcd8718446731d5b0dc608242f785f71a9026b..e23e6c0c87cb84a2716965340c5ca10cc66dd70d 100644 (file)
@@ -355,8 +355,8 @@ static const HChar* str_leak_lossmode ( Reachedness lossmode )
    switch (lossmode) {
       case Unreached:    loss = "definitely lost"; break;
       case IndirectLeak: loss = "indirectly lost"; break;
-      case Interior:     loss = "possibly lost"; break;
-      case Proper:       loss = "still reachable"; break;
+      case Possible:     loss = "possibly lost"; break;
+      case Reachable:    loss = "still reachable"; break;
    }
    return loss;
 }
@@ -367,8 +367,8 @@ static const HChar* xml_leak_kind ( Reachedness lossmode )
    switch (lossmode) {
       case Unreached:    loss = "Leak_DefinitelyLost"; break;
       case IndirectLeak: loss = "Leak_IndirectlyLost"; break;
-      case Interior:     loss = "Leak_PossiblyLost"; break;
-      case Proper:       loss = "Leak_StillReachable"; break;
+      case Possible:     loss = "Leak_PossiblyLost"; break;
+      case Reachable:    loss = "Leak_StillReachable"; break;
    }
    return loss;
 }
@@ -574,20 +574,20 @@ void MC_(pp_Error) ( Error* err )
             VG_(message)(Vg_UserMsg, "");
          }
 
-         if (l->indirect_bytes) {
+         if (l->indirect_szB) {
             VG_(message)(Vg_UserMsg, 
                "%s%'lu (%'lu direct, %'lu indirect) bytes in %'u blocks"
                " are %s in loss record %'u of %'u%s",
                xpre,
-               l->total_bytes + l->indirect_bytes
-               l->total_bytes, l->indirect_bytes, l->num_blocks,
+               l->total_bytes + l->indirect_szB
+               l->total_bytes, l->indirect_szB, l->num_blocks,
                str_leak_lossmode(l->loss_mode), n_this_record, n_total_records,
                xpost
             );
             if (VG_(clo_xml)) {
                // Nb: don't put commas in these XML numbers 
                VG_(message)(Vg_UserMsg, "  <leakedbytes>%lu</leakedbytes>", 
-                                        l->total_bytes + l->indirect_bytes);
+                                        l->total_bytes + l->indirect_szB);
                VG_(message)(Vg_UserMsg, "  <leakedblocks>%u</leakedblocks>", 
                                         l->num_blocks);
             }
index 0f5f66afa929a261bd301ea15546ef11e41564ea..5d4e8f38b4bd76d20dc4c85ac1cc7783961975df 100644 (file)
@@ -55,14 +55,15 @@ typedef
    }
    MC_AllocKind;
    
-/* Nb: first two fields must match core's VgHashNode. */
+/* This describes a heap block. Nb: first two fields must match core's
+ * VgHashNode. */
 typedef
    struct _MC_Chunk {
       struct _MC_Chunk* next;
-      Addr         data;            // ptr to actual block
-      SizeT        szB : (sizeof(UWord)*8)-2; // size requested; 30 or 62 bits
-      MC_AllocKind allockind : 2;   // which wrapper did the allocation
-      ExeContext*  where;           // where it was allocated
+      Addr         data;            // Address of the actual block.
+      SizeT        szB : (sizeof(SizeT)*8)-2; // Size requested; 30 or 62 bits.
+      MC_AllocKind allockind : 2;   // Which operation did the allocation.
+      ExeContext*  where;           // Where it was allocated.
    }
    MC_Chunk;
 
@@ -227,18 +228,15 @@ HChar* MC_(event_ctr_name)[N_PROF_EVENTS];
 /*--- Leak checking                                        ---*/
 /*------------------------------------------------------------*/
 
-/* A block is either 
-   -- Proper-ly reached; a pointer to its start has been found
-   -- Interior-ly reached; only an interior pointer to it has been found
-   -- Unreached; so far, no pointers to any part of it have been found. 
-   -- IndirectLeak; leaked, but referred to by another leaked block
-*/
 typedef 
    enum { 
-      Unreached    =0, 
-      IndirectLeak =1,
-      Interior     =2, 
-      Proper       =3
+      Unreached    =0,  // Not reached, ie. leaked. 
+                        //   (At best, only reachable from itself via a cycle.
+      IndirectLeak =1,  // Leaked, but reachable from another leaked block
+                        //   (be it Unreached or IndirectLeak).
+      Possible     =2,  // Possibly reachable from root-set;  involves at
+                        //   least one interior-pointer along the way.
+      Reachable    =3   // Definitely reachable from root-set.
   }
   Reachedness;
 
@@ -274,16 +272,15 @@ typedef
       Reachedness  loss_mode;
       /* Number of blocks and total # bytes involved. */
       SizeT        total_bytes;
-      SizeT        indirect_bytes;
+      SizeT        indirect_szB;
       UInt         num_blocks;
    }
    LossRecord;
 
-void MC_(do_detect_memory_leaks) (
-        ThreadId tid, LeakCheckMode mode,
-        Bool (*is_within_valid_secondary) ( Addr ),
-        Bool (*is_valid_aligned_word)     ( Addr )
-     );
+void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckMode mode );
+
+Bool MC_(is_valid_aligned_word)     ( Addr a );
+Bool MC_(is_within_valid_secondary) ( Addr a );
 
 void MC_(pp_LeakError)(UInt n_this_record, UInt n_total_records,
                        LossRecord* l);
index b465c5244b42695bcb6b6dc7aed12bd23dce9c30..7b9a9bcfd62f1aa199d4cb9415071a6adb9400c3 100644 (file)
 
 #include <setjmp.h>                 // For jmp_buf
 
-
-/* Define to debug the memory-leak-detector. */
-#define VG_DEBUG_LEAKCHECK 0
-#define VG_DEBUG_CLIQUE           0
-
 /*------------------------------------------------------------*/
-/*--- Low-level address-space scanning, for the leak       ---*/
-/*--- detector.                                            ---*/
+/*--- An overview of leak checking.                        ---*/
 /*------------------------------------------------------------*/
 
-static 
-jmp_buf memscan_jmpbuf;
-
-
-static
-void scan_all_valid_memory_catcher ( Int sigNo, Addr addr )
-{
-   if (0)
-      VG_(printf)("OUCH! sig=%d addr=%#lx\n", sigNo, addr);
-   if (sigNo == VKI_SIGSEGV || sigNo == VKI_SIGBUS)
-      __builtin_longjmp(memscan_jmpbuf, 1);
-}
-
-
-/* TODO: GIVE THIS A PROPER HOME
-   TODO: MERGE THIS WITH DUPLICATE IN m_main.c and coredump-elf.c.
-   Extract from aspacem a vector of the current segment start
-   addresses.  The vector is dynamically allocated and should be freed
-   by the caller when done.  REQUIRES m_mallocfree to be running.
-   Writes the number of addresses required into *n_acquired. */
-
-static Addr* get_seg_starts ( /*OUT*/Int* n_acquired )
-{
-   Addr* starts;
-   Int   n_starts, r = 0;
+// Leak-checking is a directed-graph traversal problem.  The graph has
+// two kinds of nodes:
+// - root-set nodes:
+//   - GP registers of all threads;
+//   - valid, aligned, pointer-sized data words in valid client memory,
+//     including stacks, but excluding words within client heap-allocated
+//     blocks (they are excluded so that later on we can differentiate
+//     between heap blocks that are indirectly leaked vs. directly leaked).
+// - heap-allocated blocks.  A block is a mempool chunk or a malloc chunk
+//   that doesn't contain a mempool chunk.  Nb: the terms "blocks" and
+//   "chunks" are used interchangeably below.
+//
+// There are two kinds of edges:
+// - start-pointers, i.e. pointers to the start of a block;
+// - interior-pointers, i.e. pointers to the interior of a block.
+//
+// We use "pointers" rather than "edges" below.
+//
+// Root set nodes only point to blocks.  Blocks only point to blocks;
+// a block can point to itself.
+//
+// The aim is to traverse the graph and determine the status of each block.
+//
+// There are 9 distinct cases.  See memcheck/docs/mc-manual.xml for details.
+// Presenting all nine categories to the user is probably too much.
+// Currently we do this:
+// - definitely lost:  case 3
+// - indirectly lost:  case 4, 9
+// - possibly lost:    cases 5..8
+// - still reachable:  cases 1, 2
+// 
+// It's far from clear that this is the best possible categorisation;  it's
+// accreted over time without any central guiding principle.
 
-   n_starts = 1;
-   while (True) {
-      starts = VG_(malloc)( "mc.gss.1", n_starts * sizeof(Addr) );
-      if (starts == NULL)
-         break;
-      r = VG_(am_get_segment_starts)( starts, n_starts );
-      if (r >= 0)
-         break;
-      VG_(free)(starts);
-      n_starts *= 2;
-   }
+/*------------------------------------------------------------*/
+/*--- XXX: Thoughts for improvement.                       ---*/
+/*------------------------------------------------------------*/
 
-   if (starts == NULL) {
-     *n_acquired = 0;
-     return NULL;
-   }
+// From the user's point of view:
+// - If they aren't using interior-pointers, they just have to fix the
+//   directly lost blocks, and the indirectly lost ones will be fixed as
+//   part of that.  Any possibly lost blocks will just be due to random
+//   pointer garbage and can be ignored.
+// 
+// - If they are using interior-pointers, the fact that they currently are not
+//   being told which ones might be directly lost vs. indirectly lost makes
+//   it hard to know where to begin.
+// 
+// All this makes me wonder if new option is warranted:
+// --follow-interior-pointers.  By default it would be off, the leak checker
+// wouldn't follow interior-pointers and there would only be 3 categories:
+// R, DL, IL.
+// 
+// If turned on, then it would show 7 categories (R, DL, IL, DR/DL, IR/IL,
+// IR/IL/DL, IL/DL).  That output is harder to understand but it's your own
+// damn fault for using interior-pointers...
+//
+// ----
+//
+// Also, why are two blank lines printed between each loss record?
+//
+// ----
+//
+// Also, --show-reachable is a bad name because it also turns on the showing
+// of indirectly leaked blocks(!)  It would be better named --show-all or
+// --show-all-heap-blocks, because that's the end result.
+//
+// ----
+//
+// Also, the VALGRIND_LEAK_CHECK and VALGRIND_QUICK_LEAK_CHECK aren't great
+// names.  VALGRIND_FULL_LEAK_CHECK and VALGRIND_SUMMARY_LEAK_CHECK would be
+// better.
+//
+// ----
+//
+// Also, VALGRIND_COUNT_LEAKS and VALGRIND_COUNT_LEAK_BLOCKS aren't great as
+// they combine direct leaks and indirect leaks into one.  New, more precise
+// ones (they'll need new names) would be good.  If more categories are
+// used, as per the --follow-interior-pointers option, they should be
+// updated accordingly.  And they should use a struct to return the values.
+//
+// ----
+//
+// Also, for this case:
+//
+//  (4)  p4      BBB ---> AAA
+// 
+// BBB is definitely directly lost.  AAA is definitely indirectly lost.
+// Here's the relevant loss records printed for a full check (each block is
+// 16 bytes):
+// 
+// ==20397== 16 bytes in 1 blocks are indirectly lost in loss record 9 of 15
+// ==20397==    at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397==    by 0x400521: mk (leak-cases.c:49)
+// ==20397==    by 0x400578: main (leak-cases.c:72)
+// 
+// ==20397== 32 (16 direct, 16 indirect) bytes in 1 blocks are definitely
+// lost in loss record 14 of 15
+// ==20397==    at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397==    by 0x400521: mk (leak-cases.c:49)
+// ==20397==    by 0x400580: main (leak-cases.c:72)
+// 
+// The first one is fine -- it describes AAA.
+// 
+// The second one is for BBB.  It's correct in that 16 bytes in 1 block are
+// directly lost. It's also correct that 16 are indirectly lost as a result,
+// but it means that AAA is being counted twice in the loss records.  (It's
+// not, thankfully, counted twice in the summary counts).  Argh.
+// 
+// This would be less confusing for the second one:
+// 
+// ==20397== 16 bytes in 1 blocks are definitely lost in loss record 14
+// of 15 (and 16 bytes in 1 block are indirectly lost as a result;  they
+// are mentioned elsewhere (if --show-reachable=yes is given!))
+// ==20397==    at 0x4C2694E: malloc (vg_replace_malloc.c:177)
+// ==20397==    by 0x400521: mk (leak-cases.c:49)
+// ==20397==    by 0x400580: main (leak-cases.c:72)
+// 
+// But ideally we'd present the loss record for the directly lost block and
+// then the resultant indirectly lost blocks and make it clear the
+// dependence.  Double argh.
 
-   *n_acquired = r;
-   return starts;
-}
+/*------------------------------------------------------------*/
+/*--- The actual algorithm.                                ---*/
+/*------------------------------------------------------------*/
 
+// - Find all the blocks (a.k.a. chunks) to check.  Mempool chunks require
+//   some special treatment because they can be within malloc'd blocks.
+// - Scan every word in the root set (GP registers and valid
+//   non-heap memory words).
+//   - First, we skip if it doesn't point to valid memory.
+//   - Then, we see if it points to the start or interior of a block.  If
+//     so, we push the block onto the mark stack and mark it as having been
+//     reached.
+// - Then, we process the mark stack, repeating the scanning for each block;
+//   this can push more blocks onto the mark stack.  We repeat until the
+//   mark stack is empty.  Each block is marked as definitely or possibly
+//   reachable, depending on whether interior-pointers were required to
+//   reach it.
+// - At this point we know for every block if it's reachable or not.
+// - We then push each unreached block onto the mark stack, using the block
+//   number as the "clique" number.
+// - We process the mark stack again, this time grouping blocks into cliques
+//   in order to facilitate the directly/indirectly lost categorisation.
+// - We group blocks by their ExeContexts and categorisation, and print them
+//   if --leak-check=full.  We also print summary numbers.
+//
+// A note on "cliques":
+// - A directly lost block is one with no pointers to it.  An indirectly
+//   lost block is one that is pointed to by a directly or indirectly lost
+//   block.
+// - Each directly lost block has zero or more indirectly lost blocks
+//   hanging off it.  All these blocks together form a "clique".  The
+//   directly lost block is called the "clique leader".  The clique number
+//   is the number (in lc_chunks[]) of the clique leader.
+// - Actually, a directly lost block may be pointed to if it's part of a
+//   cycle.  In that case, there may be more than one choice for the clique
+//   leader, and the choice is arbitrary.  Eg. if you have A-->B and B-->A
+//   either A or B could be the clique leader.
+// - Cliques cannot overlap, and will be truncated to avoid this.  Eg. if we
+//   have A-->C and B-->C, the two cliques will be {A,C} and {B}, or {A} and
+//   {B,C} (again the choice is arbitrary).  This is because we don't want
+//   to count a block as indirectly lost more than once.
+//
+// A note on 'is_prior_definite': 
+// - This is a boolean used in various places that indicates if the chain
+//   up to the prior node (prior to the one being considered) is definite.
+// - In the clique == -1 case: 
+//   - if True it means that the prior node is a root-set node, or that the
+//     prior node is a block which is reachable from the root-set via
+//     start-pointers.
+//   - if False it means that the prior node is a block that is only
+//     reachable from the root-set via a path including at least one
+//     interior-pointer.
+// - In the clique != -1 case, currently it's always True because we treat
+//   start-pointers and interior-pointers the same for direct/indirect leak
+//   checking.  If we added a PossibleIndirectLeak state then this would
+//   change.
+
+
+// Define to debug the memory-leak-detector.
+#define VG_DEBUG_LEAKCHECK 0
+#define VG_DEBUG_CLIQUE    0
+#define UMSG(args...)    VG_(message)(Vg_UserMsg, ##args)
 
 /*------------------------------------------------------------*/
-/*--- Detecting leaked (unreachable) malloc'd blocks.      ---*/
+/*--- Getting the initial chunks, and searching them.      ---*/
 /*------------------------------------------------------------*/
 
-/* An entry in the mark stack */
-typedef 
-   struct {
-      Int   next:30;   /* Index of next in mark stack */
-      UInt  state:2;   /* Reachedness */
-      SizeT indirect;  /* if Unreached, how much is unreachable from here */
-   } 
-   MarkStack;
-
-/* Find the i such that ptr points at or inside the block described by
-   shadows[i].  Return -1 if none found.  This assumes that shadows[]
-   has been sorted on the ->data field. */
+// Compare the MC_Chunks by 'data' (i.e. the address of the block).
+static Int compare_MC_Chunks(void* n1, void* n2)
+{
+   MC_Chunk* mc1 = *(MC_Chunk**)n1;
+   MC_Chunk* mc2 = *(MC_Chunk**)n2;
+   if (mc1->data < mc2->data) return -1;
+   if (mc1->data > mc2->data) return  1;
+   return 0;
+}
 
 #if VG_DEBUG_LEAKCHECK
-/* Used to sanity-check the fast binary-search mechanism. */
+// Used to sanity-check the fast binary-search mechanism.
 static 
-Int find_shadow_for_OLD ( Addr       ptr, 
-                          MC_Chunk** shadows,
-                          Int        n_shadows )
+Int find_chunk_for_OLD ( Addr       ptr, 
+                         MC_Chunk** chunks,
+                         Int        n_chunks )
 
 {
    Int  i;
    Addr a_lo, a_hi;
-   PROF_EVENT(70, "find_shadow_for_OLD");
-   for (i = 0; i < n_shadows; i++) {
-      PROF_EVENT(71, "find_shadow_for_OLD(loop)");
-      a_lo = shadows[i]->data;
-      a_hi = ((Addr)shadows[i]->data) + shadows[i]->szB;
+   PROF_EVENT(70, "find_chunk_for_OLD");
+   for (i = 0; i < n_chunks; i++) {
+      PROF_EVENT(71, "find_chunk_for_OLD(loop)");
+      a_lo = chunks[i]->data;
+      a_hi = ((Addr)chunks[i]->data) + chunks[i]->szB;
       if (a_lo <= ptr && ptr < a_hi)
          return i;
    }
@@ -144,32 +273,34 @@ Int find_shadow_for_OLD ( Addr       ptr,
 }
 #endif
 
-
+// Find the i such that ptr points at or inside the block described by
+// chunks[i].  Return -1 if none found.  This assumes that chunks[]
+// has been sorted on the 'data' field.
 static 
-Int find_shadow_for ( Addr       ptr, 
-                      MC_Chunk** shadows,
-                      Int        n_shadows )
+Int find_chunk_for ( Addr       ptr, 
+                     MC_Chunk** chunks,
+                     Int        n_chunks )
 {
    Addr a_mid_lo, a_mid_hi;
    Int lo, mid, hi, retVal;
-   /* VG_(printf)("find shadow for %p = ", ptr); */
+   // VG_(printf)("find chunk for %p = ", ptr);
    retVal = -1;
    lo = 0;
-   hi = n_shadows-1;
+   hi = n_chunks-1;
    while (True) {
-      /* invariant: current unsearched space is from lo to hi, inclusive. */
-      if (lo > hi) break; /* not found */
+      // Invariant: current unsearched space is from lo to hi, inclusive.
+      if (lo > hi) break; // not found
 
       mid      = (lo + hi) / 2;
-      a_mid_lo = shadows[mid]->data;
-      a_mid_hi = shadows[mid]->data + shadows[mid]->szB;
-      /* Extent of block 'mid' is [a_mid_lo .. a_mid_hi).
-         Special-case zero-sized blocks - treat them as if they had
-         size 1.  Not doing so causes them to not cover any address
-         range at all and so will never be identified as the target of
-         any pointer, which causes them to be incorrectly reported as
-         definitely leaked. */
-      if (shadows[mid]->szB == 0)
+      a_mid_lo = chunks[mid]->data;
+      a_mid_hi = chunks[mid]->data + chunks[mid]->szB;
+      // Extent of block 'mid' is [a_mid_lo .. a_mid_hi).
+      // Special-case zero-sized blocks - treat them as if they had
+      // size 1.  Not doing so causes them to not cover any address
+      // range at all and so will never be identified as the target of
+      // any pointer, which causes them to be incorrectly reported as
+      // definitely leaked.
+      if (chunks[mid]->szB == 0)
          a_mid_hi++;
 
       if (ptr < a_mid_lo) {
@@ -186,23 +317,140 @@ Int find_shadow_for ( Addr       ptr,
    }
 
 #  if VG_DEBUG_LEAKCHECK
-   tl_assert(retVal == find_shadow_for_OLD ( ptr, shadows, n_shadows ));
+   tl_assert(retVal == find_chunk_for_OLD ( ptr, chunks, n_chunks ));
 #  endif
-   /* VG_(printf)("%d\n", retVal); */
+   // VG_(printf)("%d\n", retVal);
    return retVal;
 }
 
-/* Globals, for the following callback used by VG_(detect_memory_leaks). */
-static MC_Chunk** lc_shadows;
-static Int        lc_n_shadows;
-static MarkStack* lc_markstack;
-static Int       lc_markstack_top;
-static Addr       lc_min_mallocd_addr;
-static Addr       lc_max_mallocd_addr;
-static SizeT     lc_scanned;
 
-static Bool      (*lc_is_within_valid_secondary) (Addr addr);
-static Bool      (*lc_is_valid_aligned_word)     (Addr addr);
+static MC_Chunk**
+find_active_chunks(UInt* pn_chunks)
+{
+   // Our goal is to construct a set of chunks that includes every
+   // mempool chunk, and every malloc region that *doesn't* contain a
+   // mempool chunk.
+   MC_Mempool *mp;
+   MC_Chunk **mallocs, **chunks, *mc;
+   UInt n_mallocs, n_chunks, m, s;
+   Bool *malloc_chunk_holds_a_pool_chunk;
+
+   // First we collect all the malloc chunks into an array and sort it.
+   // We do this because we want to query the chunks by interior
+   // pointers, requiring binary search.
+   mallocs = (MC_Chunk**) VG_(HT_to_array)( MC_(malloc_list), &n_mallocs );
+   if (n_mallocs == 0) {
+      tl_assert(mallocs == NULL);
+      *pn_chunks = 0;
+      return NULL;
+   }
+   VG_(ssort)(mallocs, n_mallocs, sizeof(VgHashNode*), compare_MC_Chunks);
+
+   // Then we build an array containing a Bool for each malloc chunk,
+   // indicating whether it contains any mempools.
+   malloc_chunk_holds_a_pool_chunk = VG_(calloc)( "mc.fas.1",
+                                                  n_mallocs, sizeof(Bool) );
+   n_chunks = n_mallocs;
+
+   // Then we loop over the mempool tables. For each chunk in each
+   // pool, we set the entry in the Bool array corresponding to the
+   // malloc chunk containing the mempool chunk.
+   VG_(HT_ResetIter)(MC_(mempool_list));
+   while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
+      VG_(HT_ResetIter)(mp->chunks);
+      while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
+
+         // We'll need to record this chunk.
+         n_chunks++;
+
+         // Possibly invalidate the malloc holding the beginning of this chunk.
+         m = find_chunk_for(mc->data, mallocs, n_mallocs);
+         if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
+            tl_assert(n_chunks > 0);
+            n_chunks--;
+            malloc_chunk_holds_a_pool_chunk[m] = True;
+         }
+
+         // Possibly invalidate the malloc holding the end of this chunk.
+         if (mc->szB > 1) {
+            m = find_chunk_for(mc->data + (mc->szB - 1), mallocs, n_mallocs);
+            if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
+               tl_assert(n_chunks > 0);
+               n_chunks--;
+               malloc_chunk_holds_a_pool_chunk[m] = True;
+            }
+         }
+      }
+   }
+   tl_assert(n_chunks > 0);
+
+   // Create final chunk array.
+   chunks = VG_(malloc)("mc.fas.2", sizeof(VgHashNode*) * (n_chunks));
+   s = 0;
+
+   // Copy the mempool chunks and the non-marked malloc chunks into a
+   // combined array of chunks.
+   VG_(HT_ResetIter)(MC_(mempool_list));
+   while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
+      VG_(HT_ResetIter)(mp->chunks);
+      while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
+         tl_assert(s < n_chunks);
+         chunks[s++] = mc;
+      }
+   }
+   for (m = 0; m < n_mallocs; ++m) {
+      if (!malloc_chunk_holds_a_pool_chunk[m]) {
+         tl_assert(s < n_chunks);
+         chunks[s++] = mallocs[m];
+      }
+   }
+   tl_assert(s == n_chunks);
+
+   // Free temporaries.
+   VG_(free)(mallocs);
+   VG_(free)(malloc_chunk_holds_a_pool_chunk);
+
+   *pn_chunks = n_chunks;
+
+   return chunks;
+}
+
+/*------------------------------------------------------------*/
+/*--- The leak detector proper.                            ---*/
+/*------------------------------------------------------------*/
+
+// Holds extra info about each block during leak checking.
+typedef 
+   struct {
+      UInt  state:2;    // Reachedness.
+      SizeT indirect_szB : (sizeof(SizeT)*8)-2; // If Unreached, how many bytes
+                                                //   are unreachable from here.
+   } 
+   LC_Extra;
+
+// An array holding pointers to every chunk we're checking.  Sorted by address.
+static MC_Chunk** lc_chunks;
+// How many chunks we're dealing with.
+static Int        lc_n_chunks;
+
+// This has the same number of entries as lc_chunks, and each entry
+// in lc_chunks corresponds with the entry here (ie. lc_chunks[i] and
+// lc_extras[i] describe the same block).
+static LC_Extra* lc_extras;
+
+// Records chunks that are currently being processed.  Each element in the
+// stack is an index into lc_chunks and lc_extras.  Its size is
+// 'lc_n_chunks' because in the worst case that's how many chunks could be
+// pushed onto it (actually I think the maximum is lc_n_chunks-1 but let's
+// be conservative).
+static Int* lc_markstack;
+// The index of the top element of the stack; -1 if the stack is empty, 0 if
+// the stack has one element, 1 if it has two, etc.
+static Int  lc_markstack_top;    
+
+// Keeps track of how many bytes of memory we've scanned, for printing.
+// (Nb: We don't keep track of how many register bytes we've scanned.)
+static SizeT lc_scanned_szB;
 
 
 SizeT MC_(bytes_leaked)     = 0;
@@ -217,162 +465,266 @@ SizeT MC_(blocks_dubious)    = 0;
 SizeT MC_(blocks_reachable)  = 0;
 SizeT MC_(blocks_suppressed) = 0;
 
-static Int lc_compar(void* n1, void* n2)
+
+/* TODO: GIVE THIS A PROPER HOME
+   TODO: MERGE THIS WITH DUPLICATE IN m_main.c and coredump-elf.c.
+   Extract from aspacem a vector of the current segment start
+   addresses.  The vector is dynamically allocated and should be freed
+   by the caller when done.  REQUIRES m_mallocfree to be running.
+   Writes the number of addresses required into *n_acquired. */
+
+static Addr* get_seg_starts ( /*OUT*/Int* n_acquired )
 {
-   MC_Chunk* mc1 = *(MC_Chunk**)n1;
-   MC_Chunk* mc2 = *(MC_Chunk**)n2;
-   if (mc1->data < mc2->data) return -1;
-   if (mc1->data > mc2->data) return  1;
-   return 0;
+   Addr* starts;
+   Int   n_starts, r = 0;
+
+   n_starts = 1;
+   while (True) {
+      starts = VG_(malloc)( "mc.gss.1", n_starts * sizeof(Addr) );
+      if (starts == NULL)
+         break;
+      r = VG_(am_get_segment_starts)( starts, n_starts );
+      if (r >= 0)
+         break;
+      VG_(free)(starts);
+      n_starts *= 2;
+   }
+
+   if (starts == NULL) {
+     *n_acquired = 0;
+     return NULL;
+   }
+
+   *n_acquired = r;
+   return starts;
 }
 
-/* If ptr is pointing to a heap-allocated block which hasn't been seen
-   before, push it onto the mark stack.  Clique is the index of the
-   clique leader; -1 if none. */
-static void lc_markstack_push_WRK(Addr ptr, Int clique)
+
+// Determines if a pointer is to a chunk.  Returns the chunk number et al
+// via call-by-reference.
+static Bool
+lc_is_a_chunk_ptr(Addr ptr, Int* pch_no, MC_Chunk** pch, LC_Extra** pex)
 {
-   Int sh_no;
+   Int ch_no;
+   MC_Chunk* ch;
+   LC_Extra* ex;
 
-   /* quick filter */
-   if (!VG_(am_is_valid_for_client)(ptr, 1, VKI_PROT_NONE))
-      return;
+   // Quick filter.
+   if (!VG_(am_is_valid_for_client)(ptr, 1, VKI_PROT_READ)) {
+      return False;
+   } else {
+      ch_no = find_chunk_for(ptr, lc_chunks, lc_n_chunks);
+      tl_assert(ch_no >= -1 && ch_no < lc_n_chunks);
 
-   sh_no = find_shadow_for(ptr, lc_shadows, lc_n_shadows);
+      if (ch_no == -1) {
+         return False;
+      } else {
+         // Ok, we've found a pointer to a chunk.  Get the MC_Chunk and its
+         // LC_Extra.
+         ch = lc_chunks[ch_no];
+         ex = &(lc_extras[ch_no]);
 
-   if (VG_DEBUG_LEAKCHECK)
-      VG_(printf)("ptr=%#lx -> block %d\n", ptr, sh_no);
+         tl_assert(ptr >= ch->data);
+         tl_assert(ptr < ch->data + ch->szB + (ch->szB==0  ? 1  : 0));
 
-   if (sh_no == -1)
-      return;
+         if (VG_DEBUG_LEAKCHECK)
+            VG_(printf)("ptr=%#lx -> block %d\n", ptr, ch_no);
 
-   tl_assert(sh_no >= 0 && sh_no < lc_n_shadows);
-   tl_assert(ptr >= lc_shadows[sh_no]->data);
-   tl_assert(ptr < lc_shadows[sh_no]->data 
-                   + lc_shadows[sh_no]->szB
-                   + (lc_shadows[sh_no]->szB==0  ? 1  : 0));
+         *pch_no = ch_no;
+         *pch    = ch;
+         *pex    = ex;
 
-   if (lc_markstack[sh_no].state == Unreached) {
-      if (0)
-        VG_(printf)("pushing %#lx-%#lx\n", lc_shadows[sh_no]->data,
-                    lc_shadows[sh_no]->data + lc_shadows[sh_no]->szB);
+         return True;
+      }
+   }
+}
 
-      tl_assert(lc_markstack[sh_no].next == -1);
-      lc_markstack[sh_no].next = lc_markstack_top;
-      lc_markstack_top = sh_no;
+// Push a chunk (well, just its index) onto the mark stack.
+static void lc_push(Int ch_no, MC_Chunk* ch)
+{
+   if (0) {
+      VG_(printf)("pushing %#lx-%#lx\n", ch->data, ch->data + ch->szB);
    }
+   lc_markstack_top++;
+   tl_assert(lc_markstack_top < lc_n_chunks);
+   lc_markstack[lc_markstack_top] = ch_no;
+}
 
-   tl_assert(clique >= -1 && clique < lc_n_shadows);
-
-   if (clique != -1) {
-      if (0)
-        VG_(printf)("mopup: %d: %#lx is %d\n",
-                    sh_no, lc_shadows[sh_no]->data, lc_markstack[sh_no].state);
-
-      /* An unmarked block - add it to the clique.  Add its size to
-        the clique-leader's indirect size.  If the new block was
-        itself a clique leader, it isn't any more, so add its
-        indirect to the new clique leader.
-
-        If this block *is* the clique leader, it means this is a
-        cyclic structure, so none of this applies. */
-      if (lc_markstack[sh_no].state == Unreached) {
-        lc_markstack[sh_no].state = IndirectLeak;
-
-        if (sh_no != clique) {
-           if (VG_DEBUG_CLIQUE) {
-              if (lc_markstack[sh_no].indirect)
-                 VG_(printf)("  clique %d joining clique %d adding %lu+%lu bytes\n", 
-                             sh_no, clique, 
-                             lc_shadows[sh_no]->szB + 0UL,
-                              lc_markstack[sh_no].indirect);
-              else
-                 VG_(printf)("  %d joining %d adding %lu\n", 
-                             sh_no, clique,
-                              lc_shadows[sh_no]->szB + 0UL);
-           }
-
-           lc_markstack[clique].indirect += lc_shadows[sh_no]->szB;
-           lc_markstack[clique].indirect += lc_markstack[sh_no].indirect;
-           lc_markstack[sh_no].indirect = 0; /* shouldn't matter */
-        }
-      }
-   } else if (ptr == lc_shadows[sh_no]->data) {
-      lc_markstack[sh_no].state = Proper;
+// Return the index of the chunk on the top of the mark stack, or -1 if
+// there isn't one.
+static Bool lc_pop(Int* ret)
+{
+   if (-1 == lc_markstack_top) {
+      return False;
    } else {
-      if (lc_markstack[sh_no].state == Unreached)
-        lc_markstack[sh_no].state = Interior;
+      tl_assert(0 <= lc_markstack_top && lc_markstack_top < lc_n_chunks);
+      *ret = lc_markstack[lc_markstack_top];
+      lc_markstack_top--;
+      return True;
+   }
+}
+
+
+// If 'ptr' is pointing to a heap-allocated block which hasn't been seen
+// before, push it onto the mark stack.
+static void
+lc_push_without_clique_if_a_chunk_ptr(Addr ptr, Bool is_prior_definite)
+{
+   Int ch_no;
+   MC_Chunk* ch;
+   LC_Extra* ex;
+
+   if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
+      return;
+
+   // Only push it if it hasn't been seen previously.
+   if (ex->state == Unreached) {
+      lc_push(ch_no, ch);
+   }
+
+   // Possibly upgrade the state, ie. one of:
+   // - Unreached --> Possible
+   // - Unreached --> Reachable 
+   // - Possible  --> Reachable
+   if (ptr == ch->data && is_prior_definite) {
+      // 'ptr' points to the start of the block, and the prior node is
+      // definite, which means that this block is definitely reachable.
+      ex->state = Reachable;
+
+   } else if (ex->state == Unreached) {
+      // Either 'ptr' is a interior-pointer, or the prior node isn't definite,
+      // which means that we can only mark this block as possibly reachable.
+      ex->state = Possible;
    }
 }
 
-static void lc_markstack_push(Addr ptr)
+static void
+lc_push_if_a_chunk_ptr_register(Addr ptr)
 {
-   lc_markstack_push_WRK(ptr, -1);
+   lc_push_without_clique_if_a_chunk_ptr(ptr, /*is_prior_definite*/True);
 }
 
-/* Return the top of the mark stack, if any. */
-static Int lc_markstack_pop(void)
+// If ptr is pointing to a heap-allocated block which hasn't been seen
+// before, push it onto the mark stack.  Clique is the index of the
+// clique leader.
+static void
+lc_push_with_clique_if_a_chunk_ptr(Addr ptr, Int clique)
 {
-   Int ret = lc_markstack_top;
+   Int ch_no;
+   MC_Chunk* ch;
+   LC_Extra* ex;
+
+   tl_assert(0 <= clique && clique < lc_n_chunks);
+
+   if ( ! lc_is_a_chunk_ptr(ptr, &ch_no, &ch, &ex) )
+      return;
 
-   if (ret != -1) {
-      lc_markstack_top = lc_markstack[ret].next;
-      lc_markstack[ret].next = -1;
+   // If it's not Unreached, it's already been handled so ignore it.
+   // If ch_no==clique, it's the clique leader, which means this is a cyclic
+   // structure;  again ignore it because it's already been handled.
+   if (ex->state == Unreached && ch_no != clique) {
+      // Note that, unlike reachable blocks, we currently don't distinguish
+      // between start-pointers and interior-pointers here.  We probably
+      // should, though.
+      ex->state = IndirectLeak;
+      lc_push(ch_no, ch);
+
+      // Add the block to the clique, and add its size to the
+      // clique-leader's indirect size.  Also, if the new block was
+      // itself a clique leader, it isn't any more, so add its
+      // indirect_szB to the new clique leader.
+      if (VG_DEBUG_CLIQUE) {
+         if (ex->indirect_szB > 0)
+            VG_(printf)("  clique %d joining clique %d adding %lu+%lu\n", 
+                        ch_no, clique, (SizeT)ch->szB, (SizeT)ex->indirect_szB);
+         else
+            VG_(printf)("  block %d joining clique %d adding %lu\n", 
+                        ch_no, clique, (SizeT)ch->szB);
+      }
+
+      lc_extras[clique].indirect_szB += ch->szB;
+      lc_extras[clique].indirect_szB += ex->indirect_szB;
+      ex->indirect_szB = 0;    // Shouldn't matter.
    }
+}
 
-   return ret;
+static void
+lc_push_if_a_chunk_ptr(Addr ptr, Int clique, Bool is_prior_definite)
+{
+   if (-1 == clique) 
+      lc_push_without_clique_if_a_chunk_ptr(ptr, is_prior_definite);
+   else
+      lc_push_with_clique_if_a_chunk_ptr(ptr, clique);
 }
 
 
-/* Scan a block of memory between [start, start+len).  This range may
-   be bogus, inaccessable, or otherwise strange; we deal with it.
+static jmp_buf memscan_jmpbuf;
 
-   If clique != -1, it means we're gathering leaked memory into
-   cliques, and clique is the index of the current clique leader. */
-static void lc_scan_memory_WRK(Addr start, SizeT len, Int clique)
+static
+void scan_all_valid_memory_catcher ( Int sigNo, Addr addr )
+{
+   if (0)
+      VG_(printf)("OUCH! sig=%d addr=%#lx\n", sigNo, addr);
+   if (sigNo == VKI_SIGSEGV || sigNo == VKI_SIGBUS)
+      __builtin_longjmp(memscan_jmpbuf, 1);
+}
+
+// Scan a block of memory between [start, start+len).  This range may
+// be bogus, inaccessable, or otherwise strange; we deal with it.  For each
+// valid aligned word we assume it's a pointer to a chunk a push the chunk
+// onto the mark stack if so.
+static void
+lc_scan_memory(Addr start, SizeT len, Bool is_prior_definite, Int clique)
 {
-   Addr ptr = VG_ROUNDUP(start, sizeof(Addr));
+   Addr ptr = VG_ROUNDUP(start,     sizeof(Addr));
    Addr end = VG_ROUNDDN(start+len, sizeof(Addr));
    vki_sigset_t sigmask;
 
    if (VG_DEBUG_LEAKCHECK)
-      VG_(printf)("scan %#lx-%#lx\n", start, start+len);
+      VG_(printf)("scan %#lx-%#lx (%lu)\n", start, end, len);
+
    VG_(sigprocmask)(VKI_SIG_SETMASK, NULL, &sigmask);
    VG_(set_fault_catcher)(scan_all_valid_memory_catcher);
 
-   //   lc_scanned += end-ptr;
-
+   // We might be in the middle of a page.  Do a cheap check to see if
+   // it's valid;  if not, skip onto the next page.
    if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ))
-      ptr = VG_PGROUNDUP(ptr+1);       /* first page bad */
+      ptr = VG_PGROUNDUP(ptr+1);        // First page is bad - skip it.
 
    while (ptr < end) {
       Addr addr;
 
-      /* Skip invalid chunks */
-      if (!(*lc_is_within_valid_secondary)(ptr)) {
-        ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
-        continue;
+      // Skip invalid chunks.
+      if ( ! MC_(is_within_valid_secondary)(ptr) ) {
+         ptr = VG_ROUNDUP(ptr+1, SM_SIZE);
+         continue;
       }
 
-      /* Look to see if this page seems reasonble */
+      // Look to see if this page seems reasonable.
       if ((ptr % VKI_PAGE_SIZE) == 0) {
-        if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ))
-           ptr += VKI_PAGE_SIZE; /* bad page - skip it */
+         if (!VG_(am_is_valid_for_client)(ptr, sizeof(Addr), VKI_PROT_READ)) {
+            ptr += VKI_PAGE_SIZE;      // Bad page - skip it.
+            continue;
+         }
       }
 
       if (__builtin_setjmp(memscan_jmpbuf) == 0) {
-        if ((*lc_is_valid_aligned_word)(ptr)) {
-            lc_scanned += sizeof(Addr);
-           addr = *(Addr *)ptr;
-           lc_markstack_push_WRK(addr, clique);
-        } else if (0 && VG_DEBUG_LEAKCHECK)
-           VG_(printf)("%#lx not valid\n", ptr);
-        ptr += sizeof(Addr);
+         if ( MC_(is_valid_aligned_word)(ptr) ) {
+            lc_scanned_szB += sizeof(Addr);
+            addr = *(Addr *)ptr;
+            // If we get here, the scanned word is in valid memory.  Now
+            // let's see if its contents point to a chunk.
+            lc_push_if_a_chunk_ptr(addr, clique, is_prior_definite);
+         } else if (0 && VG_DEBUG_LEAKCHECK) {
+            VG_(printf)("%#lx not valid\n", ptr);
+         }
+         ptr += sizeof(Addr);
       } else {
-        /* We need to restore the signal mask, because we were
-           longjmped out of a signal handler. */
-        VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL);
+         // We need to restore the signal mask, because we were
+         // longjmped out of a signal handler.
+         VG_(sigprocmask)(VKI_SIG_SETMASK, &sigmask, NULL);
 
-        ptr = VG_PGROUNDUP(ptr+1);     /* bad page - skip it */
+         ptr = VG_PGROUNDUP(ptr+1);     // Bad page - skip it.
       }
    }
 
@@ -381,135 +733,97 @@ static void lc_scan_memory_WRK(Addr start, SizeT len, Int clique)
 }
 
 
-static void lc_scan_memory(Addr start, SizeT len)
+// Process the mark stack until empty.
+static void lc_process_markstack(Int clique)
 {
-   if (VG_(clo_verbosity) > 2) {
-      VG_(message)(Vg_DebugMsg, "  Scanning segment: %#lx..%#lx (%ld)",
-                   start, start+len-1, len);
-   }
-   lc_scan_memory_WRK(start, len, -1);
-}
+   Int  top;
+   Bool is_prior_definite;
 
-/* Process the mark stack until empty.  If mopup is true, then we're
-   actually gathering leaked blocks, so they should be marked
-   IndirectLeak. */
-static void lc_do_leakcheck(Int clique)
-{
-   Int top;
+   while (lc_pop(&top)) {
+      tl_assert(top >= 0 && top < lc_n_chunks);      
 
-   while((top = lc_markstack_pop()) != -1) {
-      tl_assert(top >= 0 && top < lc_n_shadows);      
-      tl_assert(lc_markstack[top].state != Unreached);
+      // See comment about 'is_prior_definite' at the top to understand this.
+      is_prior_definite = ( Possible != lc_extras[top].state );
 
-      lc_scan_memory_WRK(lc_shadows[top]->data, lc_shadows[top]->szB, clique);
+      lc_scan_memory(lc_chunks[top]->data, lc_chunks[top]->szB,
+                     is_prior_definite, clique);
    }
 }
 
-static void full_report(ThreadId tid)
+static void print_results(ThreadId tid, Bool is_full_check)
 {
-   Int i;
-   Int    n_lossrecords;
+   Int         i, n_lossrecords;
    LossRecord* errlist;
    LossRecord* p;
-   Bool   is_suppressed;
-
-   /* Go through and group lost structures into cliques.  For each
-      Unreached block, push it onto the mark stack, and find all the
-      blocks linked to it.  These are marked IndirectLeak, and their
-      size is added to the clique leader's indirect size.  If one of
-      the found blocks was itself a clique leader (from a previous
-      pass), then the cliques are merged. */
-   for (i = 0; i < lc_n_shadows; i++) {
-      if (VG_DEBUG_CLIQUE)
-        VG_(printf)("cliques: %d at %#lx -> Loss state %d\n",
-                    i, lc_shadows[i]->data, lc_markstack[i].state);
-      if (lc_markstack[i].state != Unreached)
-        continue;
-
-      tl_assert(lc_markstack_top == -1);
-
-      if (VG_DEBUG_CLIQUE)
-        VG_(printf)("%d: gathering clique %#lx\n", i, lc_shadows[i]->data);
-      
-      lc_markstack_push_WRK(lc_shadows[i]->data, i);
-
-      lc_do_leakcheck(i);
+   Bool        is_suppressed;
 
-      tl_assert(lc_markstack_top == -1);
-      tl_assert(lc_markstack[i].state == IndirectLeak
-                /* jrs 20051218: Ashley Pittman supplied a
-                   custom-allocator test program which causes the ==
-                   IndirectLeak condition to fail - it causes .state
-                   to be Unreached.  Since I have no idea how this
-                   clique stuff works and no time to figure it out,
-                   just allow that condition too.  This could well be
-                   a completely bogus fix.  It doesn't seem unsafe
-                   given that in any case the .state field is
-                   immediately overwritten by the next statement. */
-                || lc_markstack[i].state == Unreached);
-
-      lc_markstack[i].state = Unreached; /* Return to unreached state,
-                                           to indicate its a clique
-                                           leader */
-   }
-      
-   /* Common up the lost blocks so we can print sensible error messages. */
+   // Common up the lost blocks so we can print sensible error messages.
    n_lossrecords = 0;
    errlist       = NULL;
-   for (i = 0; i < lc_n_shadows; i++) {
-      ExeContext* where = lc_shadows[i]->where;
+   for (i = 0; i < lc_n_chunks; i++) {
+      MC_Chunk* ch = lc_chunks[i];
+      LC_Extra* ex = &(lc_extras)[i];
 
       for (p = errlist; p != NULL; p = p->next) {
-         if (p->loss_mode == lc_markstack[i].state
+         if (p->loss_mode == ex->state
              && VG_(eq_ExeContext) ( MC_(clo_leak_resolution),
                                      p->allocated_at, 
-                                     where) ) {
+                                     ch->where) ) {
             break;
-        }
+         }
       }
       if (p != NULL) {
-         p->num_blocks  ++;
-         p->total_bytes += lc_shadows[i]->szB;
-        p->indirect_bytes += lc_markstack[i].indirect;
+         p->num_blocks++;
+         p->total_bytes  += ch->szB;
+         p->indirect_szB += ex->indirect_szB;
       } else {
-         n_lossrecords ++;
+         n_lossrecords++;
          p = VG_(malloc)( "mc.fr.1", sizeof(LossRecord));
-         p->loss_mode    = lc_markstack[i].state;
-         p->allocated_at = where;
-         p->total_bytes  = lc_shadows[i]->szB;
-        p->indirect_bytes = lc_markstack[i].indirect;
+         p->loss_mode    = ex->state;
+         p->allocated_at = ch->where;
+         p->total_bytes  = ch->szB;
+         p->indirect_szB = ex->indirect_szB;
          p->num_blocks   = 1;
          p->next         = errlist;
          errlist         = p;
       }
    }
 
-   /* Print out the commoned-up blocks and collect summary stats. */
+   MC_(blocks_leaked)     = MC_(bytes_leaked)     = 0;
+   MC_(blocks_indirect)   = MC_(bytes_indirect)   = 0;
+   MC_(blocks_dubious)    = MC_(bytes_dubious)    = 0;
+   MC_(blocks_reachable)  = MC_(bytes_reachable)  = 0;
+   MC_(blocks_suppressed) = MC_(bytes_suppressed) = 0;
+
+   // Print out the commoned-up blocks and collect summary stats.
    for (i = 0; i < n_lossrecords; i++) {
       Bool        print_record;
       LossRecord* p_min = NULL;
       SizeT       n_min = ~(0x0L);
       for (p = errlist; p != NULL; p = p->next) {
          if (p->num_blocks > 0 && p->total_bytes < n_min) {
-            n_min = p->total_bytes + p->indirect_bytes;
+            n_min = p->total_bytes + p->indirect_szB;
             p_min = p;
          }
       }
       tl_assert(p_min != NULL);
 
-      /* Ok to have tst==NULL;  it's only used if --gdb-attach=yes, and
-         we disallow that when --leak-check=yes.  
-         
-         Prints the error if not suppressed, unless it's reachable (Proper
-         or IndirectLeak) and --show-reachable=no */
-
-      print_record = ( MC_(clo_show_reachable) || 
-                      Unreached == p_min->loss_mode || 
-                       Interior == p_min->loss_mode );
-
-      // Nb: because VG_(unique_error) does all the error processing
-      // immediately, and doesn't save the error, leakExtra can be
-      // stack-allocated.
+      // Rules for printing:
+      // - We don't show suppressed loss records ever (and that's controlled
+      //   within the error manager).
+      // - We show non-suppressed loss records that are not "reachable" if 
+      //   --leak-check=yes.
+      // - We show all non-suppressed loss records if --leak-check=yes and
+      //   --show-reachable=yes.
+      //
+      // Nb: here "reachable" means Reachable *or* IndirectLeak;  note that
+      // this is different to "still reachable" used elsewhere because it
+      // includes indirectly lost blocks!
+      //
+      print_record = is_full_check &&
+                     ( MC_(clo_show_reachable) || 
+                       Unreached == p_min->loss_mode || 
+                       Possible == p_min->loss_mode );
       is_suppressed = 
          MC_(record_leak_error) ( tid, i+1, n_lossrecords, p_min,
                                   print_record );
@@ -519,339 +833,216 @@ static void full_report(ThreadId tid)
          MC_(bytes_suppressed)  += p_min->total_bytes;
 
       } else if (Unreached == p_min->loss_mode) {
-         MC_(blocks_leaked) += p_min->num_blocks;
-         MC_(bytes_leaked)  += p_min->total_bytes;
+         MC_(blocks_leaked)     += p_min->num_blocks;
+         MC_(bytes_leaked)      += p_min->total_bytes;
 
       } else if (IndirectLeak == p_min->loss_mode) {
-         MC_(blocks_indirect) += p_min->num_blocks;
-         MC_(bytes_indirect)  += p_min->total_bytes;
+         MC_(blocks_indirect)   += p_min->num_blocks;
+         MC_(bytes_indirect)    += p_min->total_bytes;
 
-      } else if (Interior == p_min->loss_mode) {
-         MC_(blocks_dubious) += p_min->num_blocks;
-         MC_(bytes_dubious)  += p_min->total_bytes;
+      } else if (Possible == p_min->loss_mode) {
+         MC_(blocks_dubious)    += p_min->num_blocks;
+         MC_(bytes_dubious)     += p_min->total_bytes;
 
-      } else if (Proper == p_min->loss_mode) {
-         MC_(blocks_reachable) += p_min->num_blocks;
-         MC_(bytes_reachable)  += p_min->total_bytes;
+      } else if (Reachable == p_min->loss_mode) {
+         MC_(blocks_reachable)  += p_min->num_blocks;
+         MC_(bytes_reachable)   += p_min->total_bytes;
 
       } else {
-         VG_(tool_panic)("generic_detect_memory_leaks: unknown loss mode");
+         VG_(tool_panic)("unknown loss mode");
       }
       p_min->num_blocks = 0;
    }
-}
-
-/* Compute a quick summary of the leak check. */
-static void make_summary(void)
-{
-   Int i;
-
-   for(i = 0; i < lc_n_shadows; i++) {
-      SizeT size = lc_shadows[i]->szB;
-
-      switch(lc_markstack[i].state) {
-      case Unreached:
-        MC_(blocks_leaked)++;
-        MC_(bytes_leaked) += size;
-        break;
-
-      case Proper:
-        MC_(blocks_reachable)++;
-        MC_(bytes_reachable) += size;
-        break;
-
-      case Interior:
-        MC_(blocks_dubious)++;
-        MC_(bytes_dubious) += size;
-        break;
-        
-      case IndirectLeak:       /* shouldn't happen */
-        MC_(blocks_indirect)++;
-        MC_(bytes_indirect) += size;
-        break;
-      }
-   }
-}
-
-static MC_Chunk**
-find_active_shadows(UInt* n_shadows)
-{
-   /* Our goal is to construct a set of shadows that includes every
-    * mempool chunk, and every malloc region that *doesn't* contain a
-    * mempool chunk. We do this in several phases.
-    *
-    * First we collect all the malloc chunks into an array and sort it.
-    * We do this because we want to query the chunks by interior
-    * pointers, requiring binary search.
-    *
-    * Second we build an array containing a Bool for each malloc chunk,
-    * indicating whether it contains any mempools.
-    *
-    * Third we loop over the mempool tables. For each chunk in each
-    * pool, we set the entry in the Bool array corresponding to the
-    * malloc chunk containing the mempool chunk.
-    *
-    * Finally we copy the mempool chunks and the non-marked malloc
-    * chunks into a combined array of shadows, free our temporaries,
-    * and return the combined array.
-    */
-
-   MC_Mempool *mp;
-   MC_Chunk **mallocs, **shadows, *mc;
-   UInt n_mallocs, m, s;
-   Bool *malloc_chunk_holds_a_pool_chunk;
-
-   mallocs = (MC_Chunk**) VG_(HT_to_array)( MC_(malloc_list), &n_mallocs );
-
-   if (n_mallocs == 0) {
-      tl_assert(mallocs == NULL);
-      *n_shadows = 0;
-      return NULL;
-   }
-
-   VG_(ssort)((void*)mallocs, n_mallocs, 
-              sizeof(VgHashNode*), lc_compar);
-
-   malloc_chunk_holds_a_pool_chunk = VG_(calloc)( "mc.fas.1",
-                                                  n_mallocs, sizeof(Bool) );
-
-   *n_shadows = n_mallocs;
-
-   VG_(HT_ResetIter)(MC_(mempool_list));
-   while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
-      VG_(HT_ResetIter)(mp->chunks);
-      while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
-
-         /* We'll need a shadow for this chunk. */
-         ++(*n_shadows);
-
-         /* Possibly invalidate the malloc holding the beginning of
-            this chunk. */
-         m = find_shadow_for(mc->data, mallocs, n_mallocs);
-         if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
-            tl_assert(*n_shadows > 0);
-            --(*n_shadows);
-            malloc_chunk_holds_a_pool_chunk[m] = True;
-         }
-
-         /* Possibly invalidate the malloc holding the end of this chunk. */
-         if (mc->szB > 1) {
-            m = find_shadow_for(mc->data + (mc->szB - 1), mallocs, n_mallocs);
-            if (m != -1 && malloc_chunk_holds_a_pool_chunk[m] == False) {
-               tl_assert(*n_shadows > 0);
-               --(*n_shadows);
-               malloc_chunk_holds_a_pool_chunk[m] = True;
-            }
-         }
-      }
-   }
 
-   tl_assert(*n_shadows > 0);
-   shadows = VG_(malloc)("mc.fas.2", sizeof(VgHashNode*) * (*n_shadows));
-   s = 0;
-
-   /* Copy the mempool chunks into the final array. */
-   VG_(HT_ResetIter)(MC_(mempool_list));
-   while ( (mp = VG_(HT_Next)(MC_(mempool_list))) ) {
-      VG_(HT_ResetIter)(mp->chunks);
-      while ( (mc = VG_(HT_Next)(mp->chunks)) ) {
-         tl_assert(s < *n_shadows);
-         shadows[s++] = mc;
+   if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
+      UMSG("");
+      UMSG("LEAK SUMMARY:");
+      UMSG("   definitely lost: %'lu bytes in %'lu blocks.",
+                               MC_(bytes_leaked), MC_(blocks_leaked) );
+      UMSG("   indirectly lost: %'lu bytes in %'lu blocks.",
+                              MC_(bytes_indirect), MC_(blocks_indirect) );
+      UMSG("     possibly lost: %'lu bytes in %'lu blocks.",
+                              MC_(bytes_dubious), MC_(blocks_dubious) );
+      UMSG("   still reachable: %'lu bytes in %'lu blocks.",
+                              MC_(bytes_reachable), MC_(blocks_reachable) );
+      UMSG("        suppressed: %'lu bytes in %'lu blocks.",
+                              MC_(bytes_suppressed), MC_(blocks_suppressed) );
+      if (!is_full_check &&
+          (MC_(blocks_leaked) + MC_(blocks_indirect) +
+           MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
+         UMSG("Rerun with --leak-check=full to see details of leaked memory.");
       }
-   }
-
-   /* Copy the malloc chunks into the final array. */
-   for (m = 0; m < n_mallocs; ++m) {
-      if (!malloc_chunk_holds_a_pool_chunk[m]) {
-         tl_assert(s < *n_shadows);
-         shadows[s++] = mallocs[m];
+      if (is_full_check &&
+          MC_(blocks_reachable) > 0 && !MC_(clo_show_reachable))
+      {
+         UMSG("Reachable blocks (those to which a pointer was found) are not shown.");
+         UMSG("To see them, rerun with: --leak-check=full --show-reachable=yes");
       }
    }
-
-   tl_assert(s == *n_shadows);
-   VG_(free)(mallocs);
-   VG_(free)(malloc_chunk_holds_a_pool_chunk);
-
-   return shadows;
 }
 
+/*------------------------------------------------------------*/
+/*--- Top-level entry point.                               ---*/
+/*------------------------------------------------------------*/
 
-/* Top level entry point to leak detector.  Call here, passing in
-   suitable address-validating functions (see comment at top of
-   scan_all_valid_memory above).  These functions used to encapsulate the
-   differences between Memcheck and Addrcheck;  they no longer do but it
-   doesn't hurt to keep them here.
-*/
-void MC_(do_detect_memory_leaks) (
-   ThreadId tid, LeakCheckMode mode,
-   Bool (*is_within_valid_secondary) ( Addr ),
-   Bool (*is_valid_aligned_word)     ( Addr )
-)
+void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckMode mode )
 {
    Int i;
    
    tl_assert(mode != LC_Off);
 
-   lc_shadows = find_active_shadows(&lc_n_shadows);
+   // Get the chunks, stop if there were none.
+   lc_chunks = find_active_chunks(&lc_n_chunks);
+   if (lc_n_chunks == 0) {
+      tl_assert(lc_chunks == NULL);
+      if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
+         UMSG("All heap blocks were freed -- no leaks are possible.");
+      }
+      return;
+   }
 
-   /* Sort the array. */
-   VG_(ssort)((void*)lc_shadows, lc_n_shadows, sizeof(VgHashNode*), lc_compar);
+   // Sort the array so blocks are in ascending order in memory.
+   VG_(ssort)(lc_chunks, lc_n_chunks, sizeof(VgHashNode*), compare_MC_Chunks);
 
-   /* Sanity check; assert that the blocks are now in order */
-   for (i = 0; i < lc_n_shadows-1; i++) {
-      tl_assert( lc_shadows[i]->data <= lc_shadows[i+1]->data);
+   // Sanity check -- make sure they're in order.
+   for (i = 0; i < lc_n_chunks-1; i++) {
+      tl_assert( lc_chunks[i]->data <= lc_chunks[i+1]->data);
    }
 
-   /* Sanity check -- make sure they don't overlap.  But do allow
-      exact duplicates.  If this assertion fails, it may mean that the
-      application has done something stupid with
-      VALGRIND_MALLOCLIKE_BLOCK client requests, specifically, has
-      made overlapping requests (which are nonsensical).  Another way
-      to screw up is to use VALGRIND_MALLOCLIKE_BLOCK for stack
-      locations; again nonsensical. */
-   for (i = 0; i < lc_n_shadows-1; i++) {
+   // Sanity check -- make sure they don't overlap.  But do allow exact
+   // duplicates.  If this assertion fails, it may mean that the application
+   // has done something stupid with VALGRIND_MALLOCLIKE_BLOCK client
+   // requests, specifically, has made overlapping requests (which are
+   // nonsensical).  Another way to screw up is to use
+   // VALGRIND_MALLOCLIKE_BLOCK for stack locations; again nonsensical.
+   for (i = 0; i < lc_n_chunks-1; i++) {
+      MC_Chunk* ch1 = lc_chunks[i];
+      MC_Chunk* ch2 = lc_chunks[i+1];
       Bool nonsense_overlap = ! (
-            /* normal case - no overlap */
-            (lc_shadows[i]->data + lc_shadows[i]->szB <= lc_shadows[i+1]->data)
-         ||
-            /* degenerate case: exact duplicates */
-              (lc_shadows[i]->data == lc_shadows[i+1]->data
-            && lc_shadows[i]->szB == lc_shadows[i+1]->szB)
+            // Normal case - no overlap.
+            (ch1->data + ch1->szB <= ch2->data) ||
+            // Degenerate case: exact duplicates.
+            (ch1->data == ch2->data && ch1->szB  == ch2->szB)
          );
       if (nonsense_overlap) {
-         VG_(message)(Vg_UserMsg, "Block [0x%lx, 0x%lx) overlaps with block [0x%lx, 0x%lx)",
-                      lc_shadows[   i]->data, (lc_shadows[   i]->data + lc_shadows[   i]->szB),
-                      lc_shadows[1+ i]->data, (lc_shadows[1+ i]->data + lc_shadows[1+ i]->szB) );
+         UMSG("Block [0x%lx, 0x%lx) overlaps with block [0x%lx, 0x%lx)",
+            ch1->data, (ch1->data + ch1->szB),
+            ch2->data, (ch2->data + ch2->szB));
       }
       tl_assert (!nonsense_overlap);
    }
 
-   if (lc_n_shadows == 0) {
-      tl_assert(lc_shadows == NULL);
-      if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
-         VG_(message)(Vg_UserMsg, 
-                      "All heap blocks were freed -- no leaks are possible.");
-      }
-      return;
+   // Initialise lc_extras.
+   lc_extras = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(LC_Extra) );
+   for (i = 0; i < lc_n_chunks; i++) {
+      lc_extras[i].state        = Unreached;
+      lc_extras[i].indirect_szB = 0;
    }
 
-   if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
-      VG_(message)(Vg_UserMsg, 
-                   "searching for pointers to %'d not-freed blocks.",
-                   lc_n_shadows );
-
-   lc_min_mallocd_addr = lc_shadows[0]->data;
-   lc_max_mallocd_addr = lc_shadows[lc_n_shadows-1]->data
-                         + lc_shadows[lc_n_shadows-1]->szB;
-
-   lc_markstack = VG_(malloc)( "mc.ddml.1",
-                               lc_n_shadows * sizeof(*lc_markstack) );
-   for (i = 0; i < lc_n_shadows; i++) {
-      lc_markstack[i].next = -1;
-      lc_markstack[i].state = Unreached;
-      lc_markstack[i].indirect = 0;
+   // Initialise lc_markstack.
+   lc_markstack = VG_(malloc)( "mc.dml.2", lc_n_chunks * sizeof(Int) );
+   for (i = 0; i < lc_n_chunks; i++) {
+      lc_markstack[i] = -1;
    }
    lc_markstack_top = -1;
 
-   lc_is_within_valid_secondary = is_within_valid_secondary;
-   lc_is_valid_aligned_word     = is_valid_aligned_word;
-
-   lc_scanned = 0;
-
-   /* Push roots onto the mark stack.  Roots are:
-      - the integer registers of all threads
-      - all mappings belonging to the client, including stacks
-      - .. but excluding any client heap segments.
-      Client heap segments are excluded because we wish to differentiate
-      client heap blocks which are referenced only from inside the heap
-      from those outside.  This facilitates the indirect vs direct loss
-      categorisation, which [if the users ever manage to understand it]
-      is really useful for detecting lost cycles.
-   */
-   { Addr*     seg_starts;
-     Int       n_seg_starts;
-     seg_starts = get_seg_starts( &n_seg_starts );
-     tl_assert(seg_starts && n_seg_starts > 0);
-     /* VG_(am_show_nsegments)( 0,"leakcheck"); */
-     for (i = 0; i < n_seg_starts; i++) {
-        NSegment const* seg = VG_(am_find_nsegment)( seg_starts[i] );
-        tl_assert(seg);
-        if (seg->kind != SkFileC && seg->kind != SkAnonC) 
-           continue;
-        if (!(seg->hasR && seg->hasW))
-           continue;
-        if (seg->isCH)
-           continue;
-
-        /* Don't poke around in device segments as this may cause
-           hangs.  Exclude /dev/zero just in case someone allocated
-           memory by explicitly mapping /dev/zero. */
-        if (seg->kind == SkFileC 
-            && (VKI_S_ISCHR(seg->mode) || VKI_S_ISBLK(seg->mode))) {
-           HChar* dev_name = VG_(am_get_filename)( (NSegment*)seg );
-           if (dev_name && 0 == VG_(strcmp)(dev_name, "/dev/zero")) {
-              /* don't skip /dev/zero */
-           } else {
-              /* skip this device mapping */
-              continue;
-           }
-        }
-
-        if (0)
-           VG_(printf)("ACCEPT %2d  %#lx %#lx\n", i, seg->start, seg->end);
-        lc_scan_memory(seg->start, seg->end+1 - seg->start);
-     }
+   // Verbosity.
+   if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
+      UMSG( "searching for pointers to %'d not-freed blocks.", lc_n_chunks );
+
+   // Scan the memory root-set, pushing onto the mark stack any blocks
+   // pointed to.
+   {
+      Int   n_seg_starts;
+      Addr* seg_starts = get_seg_starts( &n_seg_starts );
+
+      tl_assert(seg_starts && n_seg_starts > 0);
+
+      lc_scanned_szB = 0;
+
+      // VG_(am_show_nsegments)( 0, "leakcheck");
+      for (i = 0; i < n_seg_starts; i++) {
+         SizeT seg_size;
+         NSegment const* seg = VG_(am_find_nsegment)( seg_starts[i] );
+         tl_assert(seg);
+
+         if (seg->kind != SkFileC && seg->kind != SkAnonC) continue;
+         if (!(seg->hasR && seg->hasW))                    continue;
+         if (seg->isCH)                                    continue;
+
+         // Don't poke around in device segments as this may cause
+         // hangs.  Exclude /dev/zero just in case someone allocated
+         // memory by explicitly mapping /dev/zero.
+         if (seg->kind == SkFileC 
+             && (VKI_S_ISCHR(seg->mode) || VKI_S_ISBLK(seg->mode))) {
+            HChar* dev_name = VG_(am_get_filename)( (NSegment*)seg );
+            if (dev_name && 0 == VG_(strcmp)(dev_name, "/dev/zero")) {
+               // Don't skip /dev/zero.
+            } else {
+               // Skip this device mapping.
+               continue;
+            }
+         }
+
+         if (0)
+            VG_(printf)("ACCEPT %2d  %#lx %#lx\n", i, seg->start, seg->end);
+
+         // Scan the segment.  We use -1 for the clique number, because this
+         // is a root-set.
+         seg_size = seg->end - seg->start + 1;
+         if (VG_(clo_verbosity) > 2) {
+            VG_(message)(Vg_DebugMsg,
+                         "  Scanning root segment: %#lx..%#lx (%lu)",
+                         seg->start, seg->end, seg_size);
+         }
+         lc_scan_memory(seg->start, seg_size, /*is_prior_definite*/True, -1);
+      }
    }
 
-   /* Push registers onto mark stack */
-   VG_(apply_to_GP_regs)(lc_markstack_push);
+   // Scan GP registers for chunk pointers.
+   VG_(apply_to_GP_regs)(lc_push_if_a_chunk_ptr_register);
 
-   /* Keep walking the heap until everything is found */
-   lc_do_leakcheck(-1);
+   // Process the pushed blocks.  After this, every block that is reachable
+   // from the root-set has been traced.
+   lc_process_markstack(/*clique*/-1);
 
    if (VG_(clo_verbosity) > 0 && !VG_(clo_xml))
-      VG_(message)(Vg_UserMsg, "checked %'lu bytes.", lc_scanned);
+      UMSG("checked %'lu bytes.", lc_scanned_szB);
+
+   // Trace all the leaked blocks to determine which are directly leaked and
+   // which are indirectly leaked.  For each Unreached block, push it onto
+   // the mark stack, and find all the as-yet-Unreached blocks reachable
+   // from it.  These form a clique and are marked IndirectLeak, and their
+   // size is added to the clique leader's indirect size.  If one of the
+   // found blocks was itself a clique leader (from a previous clique), then
+   // the cliques are merged.
+   for (i = 0; i < lc_n_chunks; i++) {
+      MC_Chunk* ch = lc_chunks[i];
+      LC_Extra* ex = &(lc_extras[i]);
 
-   MC_(blocks_leaked)     = MC_(bytes_leaked)     = 0;
-   MC_(blocks_indirect)   = MC_(bytes_indirect)   = 0;
-   MC_(blocks_dubious)    = MC_(bytes_dubious)    = 0;
-   MC_(blocks_reachable)  = MC_(bytes_reachable)  = 0;
-   MC_(blocks_suppressed) = MC_(bytes_suppressed) = 0;
+      if (VG_DEBUG_CLIQUE)
+         VG_(printf)("cliques: %d at %#lx -> Loss state %d\n",
+                     i, ch->data, ex->state);
 
-   if (mode == LC_Full)
-      full_report(tid);
-   else
-      make_summary();
+      tl_assert(lc_markstack_top == -1);
 
-   if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
-      VG_(message)(Vg_UserMsg, "");
-      VG_(message)(Vg_UserMsg, "LEAK SUMMARY:");
-      VG_(message)(Vg_UserMsg, "   definitely lost: %'lu bytes in %'lu blocks.",
-                               MC_(bytes_leaked), MC_(blocks_leaked) );
-      if (MC_(blocks_indirect) > 0)
-        VG_(message)(Vg_UserMsg, "   indirectly lost: %'lu bytes in %'lu blocks.",
-                     MC_(bytes_indirect), MC_(blocks_indirect) );
-      VG_(message)(Vg_UserMsg, "     possibly lost: %'lu bytes in %'lu blocks.",
-                               MC_(bytes_dubious), MC_(blocks_dubious) );
-      VG_(message)(Vg_UserMsg, "   still reachable: %'lu bytes in %'lu blocks.",
-                               MC_(bytes_reachable), MC_(blocks_reachable) );
-      VG_(message)(Vg_UserMsg, "        suppressed: %'lu bytes in %'lu blocks.",
-                               MC_(bytes_suppressed), MC_(blocks_suppressed) );
-      if (mode == LC_Summary 
-          && (MC_(blocks_leaked) + MC_(blocks_indirect) 
-              + MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
-         VG_(message)(Vg_UserMsg,
-                      "Rerun with --leak-check=full to see details of leaked memory.");
-      }
-      if (MC_(blocks_reachable) > 0 && !MC_(clo_show_reachable) && mode == LC_Full) {
-         VG_(message)(Vg_UserMsg, 
-           "Reachable blocks (those to which a pointer was found) are not shown.");
-         VG_(message)(Vg_UserMsg, 
-            "To see them, rerun with: --leak-check=full --show-reachable=yes");
+      if (ex->state == Unreached) {
+         if (VG_DEBUG_CLIQUE)
+            VG_(printf)("%d: gathering clique %#lx\n", i, ch->data);
+         
+         // Push this Unreached block onto the stack and process it.
+         lc_push(i, ch);
+         lc_process_markstack(i);
+
+         tl_assert(lc_markstack_top == -1);
+         tl_assert(ex->state == Unreached);
       }
    }
+      
+   print_results( tid, ( mode == LC_Full ? True : False ) );
 
-   VG_(free) ( lc_shadows );
+   VG_(free) ( lc_chunks );
+   VG_(free) ( lc_extras );
    VG_(free) ( lc_markstack );
 }
 
index 3bec58ffc3268fe466b0fc3bddb279d285984cb5..3eba3aabee3b6832708051d3d69e5982023966dd 100644 (file)
@@ -4459,8 +4459,7 @@ static Int mc_get_or_set_vbits_for_client (
    address space is possibly in use, or not.  If in doubt return
    True.
 */
-static
-Bool mc_is_within_valid_secondary ( Addr a )
+Bool MC_(is_within_valid_secondary) ( Addr a )
 {
    SecMap* sm = maybe_get_secmap_for ( a );
    if (sm == NULL || sm == &sm_distinguished[SM_DIST_NOACCESS]
@@ -4475,15 +4474,10 @@ Bool mc_is_within_valid_secondary ( Addr a )
 
 /* For the memory leak detector, say whether or not a given word
    address is to be regarded as valid. */
-static
-Bool mc_is_valid_aligned_word ( Addr a )
+Bool MC_(is_valid_aligned_word) ( Addr a )
 {
    tl_assert(sizeof(UWord) == 4 || sizeof(UWord) == 8);
-   if (sizeof(UWord) == 4) {
-      tl_assert(VG_IS_4_ALIGNED(a));
-   } else {
-      tl_assert(VG_IS_8_ALIGNED(a));
-   }
+   tl_assert(VG_IS_WORD_ALIGNED(a));
    if (is_mem_defined( a, sizeof(UWord), NULL, NULL) == MC_Ok
        && !MC_(in_ignored_range)(a)) {
       return True;
@@ -4493,20 +4487,6 @@ Bool mc_is_valid_aligned_word ( Addr a )
 }
 
 
-/* Leak detector for this tool.  We don't actually do anything, merely
-   run the generic leak detector with suitable parameters for this
-   tool. */
-static void mc_detect_memory_leaks ( ThreadId tid, LeakCheckMode mode )
-{
-   MC_(do_detect_memory_leaks) ( 
-      tid, 
-      mode, 
-      mc_is_within_valid_secondary, 
-      mc_is_valid_aligned_word 
-   );
-}
-
-
 /*------------------------------------------------------------*/
 /*--- Initialisation                                       ---*/
 /*------------------------------------------------------------*/
@@ -4933,7 +4913,7 @@ static Bool mc_handle_client_request ( ThreadId tid, UWord* arg, UWord* ret )
       }
 
       case VG_USERREQ__DO_LEAK_CHECK:
-         mc_detect_memory_leaks(tid, arg[1] ? LC_Summary : LC_Full);
+         MC_(detect_memory_leaks)(tid, arg[1] ? LC_Summary : LC_Full);
          *ret = 0; /* return value is meaningless */
          break;
 
@@ -5590,7 +5570,7 @@ static void mc_fini ( Int exitcode )
    }
 
    if (MC_(clo_leak_check) != LC_Off)
-      mc_detect_memory_leaks(1/*bogus ThreadId*/, MC_(clo_leak_check));
+      MC_(detect_memory_leaks)(1/*bogus ThreadId*/, MC_(clo_leak_check));
 
    done_prof_mem();
 
index f192f722d3b3705a5a38fcf93f77bb2a98973c5c..0f371e1cd04ed44f1c7a3949ab3c939ca3412da4 100644 (file)
@@ -206,7 +206,7 @@ typedef
                       (unsigned long)(sizeof (__lvalue)))
 
 
-/* Do a memory leak check mid-execution.  */
+/* Do a full memory leak check (like --leak-check=full) mid-execution. */
 #define VALGRIND_DO_LEAK_CHECK                                   \
    {unsigned long _qzz_res;                                      \
     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,                      \
@@ -214,8 +214,7 @@ typedef
                             0, 0, 0, 0, 0);                      \
    }
 
-/* Just display summaries of leaked memory, rather than all the
-   details */
+/* Do a summary memory leak check (like --leak-check=summary) mid-execution. */
 #define VALGRIND_DO_QUICK_LEAK_CHECK                            \
    {unsigned long _qzz_res;                                      \
     VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0,                      \
index 0a2b36b672e53d50f5721e5707376ce6676608dd..fbf39a203406df796a5492357be0e41e0a2ac632 100644 (file)
@@ -59,6 +59,8 @@ EXTRA_DIST = $(noinst_SCRIPTS) \
        inits.stderr.exp inits.vgtest \
        inline.stderr.exp inline.stdout.exp inline.vgtest \
        leak-0.vgtest leak-0.stderr.exp \
+       leak-cases-full.vgtest leak-cases-full.stderr.exp \
+       leak-cases-summary.vgtest leak-cases-summary.stderr.exp \
        leak-cycle.vgtest leak-cycle.stderr.exp \
        leak-pool-0.vgtest leak-pool-0.stderr.exp \
        leak-pool-1.vgtest leak-pool-1.stderr.exp \
@@ -187,7 +189,9 @@ check_PROGRAMS = \
        doublefree error_counts errs1 exitprog execve execve2 erringfds \
        file_locking \
        fprw fwrite inits inline \
-       leak-0 leak-cycle leak-pool leak-tree leakotron \
+       leak-0 \
+       leak-cases \
+       leak-cycle leak-pool leak-tree leakotron \
        linux-syslog-syscall \
        linux-syscalls-2007 \
        long_namespace_xml \
index d3ae4860cfde9d28a7008864554031702785d929..728c4484e98cec67c8e3e31d9e6ecb6a4175f2f2 100755 (executable)
@@ -39,5 +39,5 @@ sed "s/checked [0-9,]* bytes./checked ... bytes./" |
 # More leak check filtering.  For systems that do extra libc allocations
 # (eg. Darwin) there may be extra (reachable, and thus not shown) loss
 # records.  So we filter out the loss record numbers.
-perl -p -e "s/lost in loss record \d+ of \d+/lost in loss record ... of .../" 
+perl -p -e "s/in loss record \d+ of \d+/in loss record ... of .../" 
 
diff --git a/memcheck/tests/leak-cases-full.stderr.exp b/memcheck/tests/leak-cases-full.stderr.exp
new file mode 100644 (file)
index 0000000..30d6556
--- /dev/null
@@ -0,0 +1,57 @@
+leaked:      80 bytes in  5 blocks
+dubious:     96 bytes in  6 blocks
+reachable:   64 bytes in  4 blocks
+suppressed:   0 bytes in  0 blocks
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:91)
+
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:91)
+
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:88)
+
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:88)
+
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:85)
+
+
+16 bytes in 1 blocks are possibly lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:82)
+
+
+16 bytes in 1 blocks are definitely lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:78)
+
+
+32 (16 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:80)
+
+
+32 (16 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record ... of ...
+   at 0x........: malloc (vg_replace_malloc.c:...)
+   by 0x........: mk (leak-cases.c:52)
+   by 0x........: main (leak-cases.c:95)
diff --git a/memcheck/tests/leak-cases-full.vgtest b/memcheck/tests/leak-cases-full.vgtest
new file mode 100644 (file)
index 0000000..7b6976c
--- /dev/null
@@ -0,0 +1,2 @@
+prog: leak-cases
+vgopts: -q --leak-check=full --leak-resolution=high
diff --git a/memcheck/tests/leak-cases-summary.stderr.exp b/memcheck/tests/leak-cases-summary.stderr.exp
new file mode 100644 (file)
index 0000000..4f811ca
--- /dev/null
@@ -0,0 +1,4 @@
+leaked:      80 bytes in  5 blocks
+dubious:     96 bytes in  6 blocks
+reachable:   64 bytes in  4 blocks
+suppressed:   0 bytes in  0 blocks
diff --git a/memcheck/tests/leak-cases-summary.vgtest b/memcheck/tests/leak-cases-summary.vgtest
new file mode 100644 (file)
index 0000000..f734cc1
--- /dev/null
@@ -0,0 +1,2 @@
+prog: leak-cases
+vgopts: -q --leak-check=summary --leak-resolution=high
diff --git a/memcheck/tests/leak-cases.c b/memcheck/tests/leak-cases.c
new file mode 100644 (file)
index 0000000..d6310eb
--- /dev/null
@@ -0,0 +1,104 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "leak.h"
+#include "../memcheck.h"
+
+// Pointer chain          AAA Category/output BBB Category/output
+// -------------          ------------------- ------------
+// p1 ---> AAA            DR / R
+// p2 ---> AAA ---> BBB   DR / R              IR / R
+// p3      AAA            DL / L
+// p4      AAA ---> BBB   DL / I              IL / L
+// p5 -?-> AAA            (y)DR, (n)DL / P
+// p6 ---> AAA -?-> BBB   DR / R              (y)IR, (n)DL / P
+// p7 -?-> AAA ---> BBB   (y)DR, (n)DL / P    (y)IR, (n)IL / P
+// p8 -?-> AAA -?-> BBB   (y)DR, (n)DL / P    (y,y)IR, (n,y)IL, (_,n)DL / P
+// p9      AAA -?-> BBB   DL / L              (y)IL, (n)DL / I 
+//
+// Pointer chain legend:
+// - pN: a root set pointer
+// - AAA, BBB: heap blocks
+// - --->: a start-pointer
+// - -?->: an interior-pointer
+//
+// Category legend:
+// - DR: Directly reachable
+// - IR: Indirectly reachable
+// - DL: Directly lost
+// - IL: Indirectly lost
+// - (y)XY: it's XY if the interior-pointer is a real pointer
+// - (n)XY: it's XY if the interior-pointer is not a real pointer
+// - (_)XY: it's XY in either case
+//
+// How we handle the 9 cases:
+// - "directly lost":    case 3
+// - "indirectly lost":  cases 4, 9
+// - "possibly lost":    cases 5..8
+// - "still reachable":  cases 1, 2
+
+
+typedef
+   struct _Node {
+      struct _Node* next;
+      // Padding ensures the structu is the same size on 32-bit and 64-bit
+      // machines.
+      char padding[8 - sizeof(struct _Node*)];
+   } Node;
+
+Node* mk(Node* next)
+{
+   // We allocate two nodes, so we can do p+1 and still point within the
+   // block.
+   Node* x = malloc(2 * sizeof(Node));
+   x->next = next;
+   return x;
+}
+
+// These are definite roots.
+Node* p1;
+Node* p2;
+Node* p3;
+Node* p4;
+Node* p5;
+Node* p6;
+Node* p7;
+Node* p8;
+Node* p9;
+
+int main(void)
+{
+   DECLARE_LEAK_COUNTERS;
+
+   GET_INITIAL_LEAK_COUNTS;
+
+   p1 = mk(NULL);       // Case 1: 16/1 still reachable
+
+   p2 = mk(mk(NULL));   // Case 2: 16/1 still reachable
+                                // 16/1 still reachable
+   (void)mk(NULL);      // Case 3: 16/1 definitely lost
+
+   (void)mk(mk(NULL));  // Case 4: 16/1 indirectly lost (counted again below!)
+                                // 32(16d,16i)/1 definitely lost (double count!)
+   p5 = mk(NULL);       // Case 5: 16/1 possibly lost (ok)
+   p5++;
+
+   p6 = mk(mk(NULL));   // Case 6: 16/1 still reachable
+   (p6->next)++;                // 16/1 possibly lost
+
+   p7 = mk(mk(NULL));   // Case 7: 16/1 possibly lost
+   p7++;                        // 16/1 possibly lost
+
+   p8 = mk(mk(NULL));   // Case 8: 16/1 possibly lost
+   (p8->next)++;                // 16/1 possibly lost
+   p8++;
+
+   p9 = mk(mk(NULL));   // Case 9: 16/1 indirectly lost (counted again below!)
+   (p9->next)++;                // 32(16d,16i)/1 definitely lost (double count!)
+   p9 = NULL;
+
+   GET_FINAL_LEAK_COUNTS;
+
+   PRINT_LEAK_COUNTS(stderr);
+
+   return 0;
+}