Memcheck:
* add delta leak checking functionality
* some editing of related manual sections
(Philippe Waroquiers, philippe.waroquiers@skynet.be).  Bug 214909
comment 105.


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@11838 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/memcheck/mc_leakcheck.c b/memcheck/mc_leakcheck.c
index 016109c..41c9b71 100644
--- a/memcheck/mc_leakcheck.c
+++ b/memcheck/mc_leakcheck.c
@@ -434,6 +434,16 @@
 static MC_Chunk** lc_chunks;
 // How many chunks we're dealing with.
 static Int        lc_n_chunks;
+// chunks will be converted and merged in loss record, maintained in lr_table
+// lr_table elements are kept from one leak_search to another to implement
+// the "print new/changed leaks" client request
+static OSet*        lr_table;
+
+// DeltaMode used the last time we called detect_memory_leaks.
+// The recorded leak errors must be output using a logic based on this delta_mode.
+// The below avoids replicating the delta_mode in each LossRecord.
+LeakCheckDeltaMode MC_(detect_memory_leaks_last_delta_mode);
+
 
 // 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
@@ -770,20 +780,35 @@
    return 0;
 }
 
-static void print_results(ThreadId tid, Bool is_full_check)
+static void print_results(ThreadId tid, LeakCheckParams lcp)
 {
    Int          i, n_lossrecords;
-   OSet*        lr_table;
    LossRecord** lr_array;
    LossRecord*  lr;
    Bool         is_suppressed;
+   SizeT        old_bytes_leaked      = MC_(bytes_leaked); /* to report delta in summary */
+   SizeT        old_bytes_indirect    = MC_(bytes_indirect); 
+   SizeT        old_bytes_dubious     = MC_(bytes_dubious); 
+   SizeT        old_bytes_reachable   = MC_(bytes_reachable); 
+   SizeT        old_bytes_suppressed  = MC_(bytes_suppressed); 
+   SizeT        old_blocks_leaked     = MC_(blocks_leaked);
+   SizeT        old_blocks_indirect   = MC_(blocks_indirect);
+   SizeT        old_blocks_dubious    = MC_(blocks_dubious);
+   SizeT        old_blocks_reachable  = MC_(blocks_reachable);
+   SizeT        old_blocks_suppressed = MC_(blocks_suppressed);
 
-   // Create the lr_table, which holds the loss records.
-   lr_table =
-      VG_(OSetGen_Create)(offsetof(LossRecord, key),
-                          cmp_LossRecordKey_LossRecord,
-                          VG_(malloc), "mc.pr.1",
-                          VG_(free)); 
+   if (lr_table == NULL)
+      // Create the lr_table, which holds the loss records.
+      // If the lr_table already exists, it means it contains
+      // loss_records from the previous leak search. The old_*
+      // values in these records are used to implement the
+      // leak check delta mode
+      lr_table =
+         VG_(OSetGen_Create)(offsetof(LossRecord, key),
+                             cmp_LossRecordKey_LossRecord,
+                             VG_(malloc), "mc.pr.1",
+                             VG_(free));
+
 
    // Convert the chunks into loss records, merging them where appropriate.
    for (i = 0; i < lc_n_chunks; i++) {
@@ -810,6 +835,9 @@
          lr->szB              = ch->szB;
          lr->indirect_szB     = ex->indirect_szB;
          lr->num_blocks       = 1;
+         lr->old_szB          = 0;
+         lr->old_indirect_szB = 0;
+         lr->old_num_blocks   = 0;
          VG_(OSetGen_Insert)(lr_table, lr);
       }
    }
@@ -837,7 +865,7 @@
 
    // Print the loss records (in size order) and collect summary stats.
    for (i = 0; i < n_lossrecords; i++) {
-      Bool count_as_error, print_record;
+      Bool count_as_error, print_record, delta_considered;
       // Rules for printing:
       // - We don't show suppressed loss records ever (and that's controlled
       //   within the error manager).
@@ -851,18 +879,37 @@
       // includes indirectly lost blocks!
       //
       lr = lr_array[i];
-      print_record = is_full_check &&
-                     ( MC_(clo_show_reachable) ||
+      switch (lcp.deltamode) {
+         case LCD_Any: 
+            delta_considered = lr->num_blocks > 0;
+            break;
+         case LCD_Increased:
+            delta_considered 
+               = lr_array[i]->szB > lr_array[i]->old_szB
+                 || lr_array[i]->indirect_szB > lr_array[i]->old_indirect_szB
+                 || lr->num_blocks > lr->old_num_blocks;
+            break;
+         case LCD_Changed: 
+            delta_considered = lr_array[i]->szB != lr_array[i]->old_szB
+            || lr_array[i]->indirect_szB != lr_array[i]->old_indirect_szB
+            || lr->num_blocks != lr->old_num_blocks;
+            break;
+         default:
+            tl_assert(0);
+      }
+
+      print_record = lcp.mode == LC_Full && delta_considered &&
+                     ( lcp.show_reachable ||
                        Unreached == lr->key.state || 
-                       ( MC_(clo_show_possibly_lost) && 
+                       ( lcp.show_possibly_lost && 
                          Possible  == lr->key.state ) );
-      // We don't count a leaks as errors with --leak-check=summary.
+      // We don't count a leaks as errors with lcp.mode==LC_Summary.
       // Otherwise you can get high error counts with few or no error
       // messages, which can be confusing.  Also, you could argue that
       // indirect leaks should be counted as errors, but it seems better to
       // make the counting criteria similar to the printing criteria.  So we
       // don't count them.
-      count_as_error = is_full_check && 
+      count_as_error = lcp.mode == LC_Full && delta_considered &&
                        ( Unreached == lr->key.state || 
                          Possible  == lr->key.state );
       is_suppressed = 
@@ -894,31 +941,74 @@
       }
    }
 
+   for (i = 0; i < n_lossrecords; i++)
+      {
+         if (lr->num_blocks == 0)
+            // remove from lr_table the old loss_records with 0 bytes found
+            VG_(OSetGen_Remove) (lr_table, &lr_array[i]->key);
+         else
+            {
+               // move the leak sizes to old_* and zero the current sizes
+               // for next leak search
+               lr_array[i]->old_szB          = lr_array[i]->szB;
+               lr_array[i]->old_indirect_szB = lr_array[i]->indirect_szB;
+               lr_array[i]->old_num_blocks   = lr_array[i]->num_blocks;
+               lr_array[i]->szB              = 0;
+               lr_array[i]->indirect_szB     = 0;
+               lr_array[i]->num_blocks       = 0;
+            }
+      }
+   VG_(free)(lr_array); 
+
    if (VG_(clo_verbosity) > 0 && !VG_(clo_xml)) {
+      char d_bytes[20];
+      char d_blocks[20];
+
       VG_(umsg)("LEAK SUMMARY:\n");
-      VG_(umsg)("   definitely lost: %'lu bytes in %'lu blocks\n",
-                MC_(bytes_leaked), MC_(blocks_leaked) );
-      VG_(umsg)("   indirectly lost: %'lu bytes in %'lu blocks\n",
-                MC_(bytes_indirect), MC_(blocks_indirect) );
-      VG_(umsg)("     possibly lost: %'lu bytes in %'lu blocks\n",
-                MC_(bytes_dubious), MC_(blocks_dubious) );
-      VG_(umsg)("   still reachable: %'lu bytes in %'lu blocks\n",
-                MC_(bytes_reachable), MC_(blocks_reachable) );
-      VG_(umsg)("        suppressed: %'lu bytes in %'lu blocks\n",
-                MC_(bytes_suppressed), MC_(blocks_suppressed) );
-      if (!is_full_check &&
+      VG_(umsg)("   definitely lost: %'lu%s bytes in %'lu%s blocks\n",
+                MC_(bytes_leaked), 
+                MC_(snprintf_delta) (d_bytes, 20, MC_(bytes_leaked), old_bytes_leaked, lcp.deltamode),
+                MC_(blocks_leaked),
+                MC_(snprintf_delta) (d_blocks, 20, MC_(blocks_leaked), old_blocks_leaked, lcp.deltamode));
+      VG_(umsg)("   indirectly lost: %'lu%s bytes in %'lu%s blocks\n",
+                MC_(bytes_indirect), 
+                MC_(snprintf_delta) (d_bytes, 20, MC_(bytes_indirect), old_bytes_indirect, lcp.deltamode),
+                MC_(blocks_indirect),
+                MC_(snprintf_delta) (d_blocks, 20, MC_(blocks_indirect), old_blocks_indirect, lcp.deltamode) );
+      VG_(umsg)("     possibly lost: %'lu%s bytes in %'lu%s blocks\n",
+                MC_(bytes_dubious), 
+                MC_(snprintf_delta) (d_bytes, 20, MC_(bytes_dubious), old_bytes_dubious, lcp.deltamode), 
+                MC_(blocks_dubious),
+                MC_(snprintf_delta) (d_blocks, 20, MC_(blocks_dubious), old_blocks_dubious, lcp.deltamode) );
+      VG_(umsg)("   still reachable: %'lu%s bytes in %'lu%s blocks\n",
+                MC_(bytes_reachable), 
+                MC_(snprintf_delta) (d_bytes, 20, MC_(bytes_reachable), old_bytes_reachable, lcp.deltamode), 
+                MC_(blocks_reachable),
+                MC_(snprintf_delta) (d_blocks, 20, MC_(blocks_reachable), old_blocks_reachable, lcp.deltamode) );
+      VG_(umsg)("        suppressed: %'lu%s bytes in %'lu%s blocks\n",
+                MC_(bytes_suppressed), 
+                MC_(snprintf_delta) (d_bytes, 20, MC_(bytes_suppressed), old_bytes_suppressed, lcp.deltamode), 
+                MC_(blocks_suppressed),
+                MC_(snprintf_delta) (d_blocks, 20, MC_(blocks_suppressed), old_blocks_suppressed, lcp.deltamode) );
+      if (lcp.mode != LC_Full &&
           (MC_(blocks_leaked) + MC_(blocks_indirect) +
            MC_(blocks_dubious) + MC_(blocks_reachable)) > 0) {
-         VG_(umsg)("Rerun with --leak-check=full to see details "
-                   "of leaked memory\n");
+         if (lcp.requested_by_monitor_command)
+            VG_(umsg)("To see details of leaked memory, give 'full' arg to mc.leak_check\n");
+         else
+            VG_(umsg)("Rerun with --leak-check=full to see details "
+                      "of leaked memory\n");
       }
-      if (is_full_check &&
-          MC_(blocks_reachable) > 0 && !MC_(clo_show_reachable))
+      if (lcp.mode == LC_Full &&
+          MC_(blocks_reachable) > 0 && !lcp.show_reachable)
       {
          VG_(umsg)("Reachable blocks (those to which a pointer "
                    "was found) are not shown.\n");
-         VG_(umsg)("To see them, rerun with: --leak-check=full "
-                   "--show-reachable=yes\n");
+         if (lcp.requested_by_monitor_command)
+            VG_(umsg)("To see them, add 'reachable any' args to mc.leak_check\n");
+         else
+            VG_(umsg)("To see them, rerun with: --leak-check=full "
+                      "--show-reachable=yes\n");
       }
       VG_(umsg)("\n");
    }
@@ -928,16 +1018,26 @@
 /*--- Top-level entry point.                               ---*/
 /*------------------------------------------------------------*/
 
-void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckMode mode )
+void MC_(detect_memory_leaks) ( ThreadId tid, LeakCheckParams lcp)
 {
    Int i, j;
    
-   tl_assert(mode != LC_Off);
+   tl_assert(lcp.mode != LC_Off);
+
+   MC_(detect_memory_leaks_last_delta_mode) = lcp.deltamode;
 
    // 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 (lr_table != NULL) {
+         // forget the previous recorded LossRecords as next leak search will in any case
+         // just create new leaks.
+         // Maybe it would be better to rather call print_result ?
+         // (at least when leak decrease are requested)
+         // This will then output all LossRecords with a size decreasing to 0
+         VG_(OSetGen_Destroy) (lr_table);
+      }
       if (VG_(clo_verbosity) >= 1 && !VG_(clo_xml)) {
          VG_(umsg)("All heap blocks were freed -- no leaks are possible\n");
          VG_(umsg)("\n");
@@ -1124,7 +1224,7 @@
       }
    }
       
-   print_results( tid, ( mode == LC_Full ? True : False ) );
+   print_results( tid, lcp);
 
    VG_(free) ( lc_chunks );
    VG_(free) ( lc_extras );