From 4fe1dc07fa989cbecf8081ca00fbc88bcc33c495 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 10 May 2019 23:15:49 -0400
Subject: [PATCH 46/54] FIXME: deduplication of diagnostics

---
 gcc/analyzer/hardcoded-state-machine-file.cc       |  16 +-
 gcc/analyzer/hardcoded-state-machine-malloc.cc     |  24 ++
 .../hardcoded-state-machine-pattern-test.cc        |  11 +-
 gcc/analyzer/hardcoded-state-machine-sensitive.cc  |  10 +-
 gcc/analyzer/hardcoded-state-machine-taint.cc      |   9 +-
 gcc/analyzer/hardcoded-state-machine.h             |  37 +++
 gcc/analyzer/impl-4.cc                             | 300 +++++++++++++++++++--
 gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c    |  24 +-
 8 files changed, 395 insertions(+), 36 deletions(-)

diff --git a/gcc/analyzer/hardcoded-state-machine-file.cc b/gcc/analyzer/hardcoded-state-machine-file.cc
index bedde78..66698ab 100644
--- a/gcc/analyzer/hardcoded-state-machine-file.cc
+++ b/gcc/analyzer/hardcoded-state-machine-file.cc
@@ -40,11 +40,17 @@ static const bool DEBUG = false;
 
 ////////////////////////////////////////////////////////////////////////////
 
-class double_fclose : public pending_diagnostic
+class double_fclose : public pending_diagnostic_subclass<double_fclose>
 {
 public:
   double_fclose (tree arg) : m_arg (arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "double_fclose"; }
+  bool operator== (const double_fclose &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     warning_at (rich_loc, 0,
@@ -56,11 +62,17 @@ private:
   tree m_arg;
 };
 
-class file_leak : public pending_diagnostic
+class file_leak : public pending_diagnostic_subclass<file_leak>
 {
 public:
   file_leak (tree arg) : m_arg (arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "file_leak"; }
+  bool operator== (const file_leak &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     metadata m;
diff --git a/gcc/analyzer/hardcoded-state-machine-malloc.cc b/gcc/analyzer/hardcoded-state-machine-malloc.cc
index 017c322..4ce5106 100644
--- a/gcc/analyzer/hardcoded-state-machine-malloc.cc
+++ b/gcc/analyzer/hardcoded-state-machine-malloc.cc
@@ -47,6 +47,11 @@ public:
   : m_sm (sm), m_arg (arg)
   {}
 
+  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
+  {
+    return m_arg == ((const malloc_diagnostic &)base_other).m_arg;
+  }
+
 protected:
   const malloc_state_machine &m_sm;
   tree m_arg;
@@ -59,6 +64,8 @@ public:
   : malloc_diagnostic (sm, arg)
   {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     auto_diagnostic_group d;
@@ -126,6 +133,8 @@ public:
   possible_null_deref (const malloc_state_machine &sm, tree arg)
   : malloc_diagnostic (sm, arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
@@ -162,6 +171,8 @@ public:
   null_deref (const malloc_state_machine &sm, tree arg)
   : malloc_diagnostic (sm, arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
@@ -178,6 +189,8 @@ public:
   : malloc_diagnostic (sm, arg)
   {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     /* CWE-416: Use After Free.  */
@@ -204,6 +217,8 @@ public:
   malloc_leak (const malloc_state_machine &sm, tree arg)
   : malloc_diagnostic (sm, arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     metadata m;
@@ -241,6 +256,15 @@ public:
   {
   }
 
+  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }
+
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
+    return (m_arg == other.m_arg && m_kind == other.m_kind);
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     auto_diagnostic_group d;
diff --git a/gcc/analyzer/hardcoded-state-machine-pattern-test.cc b/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
index e6c9567..bef3bcb 100644
--- a/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
+++ b/gcc/analyzer/hardcoded-state-machine-pattern-test.cc
@@ -40,11 +40,20 @@ static const bool DEBUG = false;
 
 ////////////////////////////////////////////////////////////////////////////
 
-class pattern_match : public pending_diagnostic
+class pattern_match : public pending_diagnostic_subclass<pattern_match>
 {
 public:
   pattern_match (tree arg, const char *msg) : m_arg (arg), m_msg (msg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "pattern_match"; }
+
+  bool operator== (const pattern_match &other) const
+  {
+    return (m_arg == other.m_arg
+	    // FIXME: should this be string equality?
+	    && m_msg == other.m_msg);
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     // TODO:
diff --git a/gcc/analyzer/hardcoded-state-machine-sensitive.cc b/gcc/analyzer/hardcoded-state-machine-sensitive.cc
index 775a8d5..49dce94 100644
--- a/gcc/analyzer/hardcoded-state-machine-sensitive.cc
+++ b/gcc/analyzer/hardcoded-state-machine-sensitive.cc
@@ -40,11 +40,19 @@ static const bool DEBUG = false;
 
 ////////////////////////////////////////////////////////////////////////////
 
-class leakage_through_output_file : public pending_diagnostic
+class leakage_through_output_file
+  : public pending_diagnostic_subclass<leakage_through_output_file>
 {
 public:
   leakage_through_output_file (tree arg) : m_arg (arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "leakage_through_output_file"; }
+
+  bool operator== (const leakage_through_output_file &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     metadata m;
diff --git a/gcc/analyzer/hardcoded-state-machine-taint.cc b/gcc/analyzer/hardcoded-state-machine-taint.cc
index 8ec4719..19731bc 100644
--- a/gcc/analyzer/hardcoded-state-machine-taint.cc
+++ b/gcc/analyzer/hardcoded-state-machine-taint.cc
@@ -40,11 +40,18 @@ static const bool DEBUG = false;
 
 ////////////////////////////////////////////////////////////////////////////
 
-class tainted_array_index : public pending_diagnostic
+class tainted_array_index : public pending_diagnostic_subclass<tainted_array_index>
 {
 public:
   tainted_array_index (tree arg) : m_arg (arg) {}
 
+  const char *get_kind () const FINAL OVERRIDE { return "tainted_array_index"; }
+
+  bool operator== (const tainted_array_index &other) const
+  {
+    return m_arg == other.m_arg;
+  }
+
   void emit (rich_location *rich_loc) FINAL OVERRIDE
   {
     metadata m;
diff --git a/gcc/analyzer/hardcoded-state-machine.h b/gcc/analyzer/hardcoded-state-machine.h
index 8832faf..e4b359f 100644
--- a/gcc/analyzer/hardcoded-state-machine.h
+++ b/gcc/analyzer/hardcoded-state-machine.h
@@ -132,8 +132,28 @@ class pending_diagnostic
 {
  public:
   virtual ~pending_diagnostic () {}
+
+  /* Hand-coded RTTI: get an ID for the subclass.  */
+  virtual const char *get_kind () const = 0;
+
   virtual void emit (rich_location *) = 0;
 
+  /* Compare for equality with OTHER, which might be of a different
+     subclass.  */
+
+  bool equal_p (const pending_diagnostic &other)
+  {
+    /* Check for pointer equality on the IDs from get_kind.  */
+    if (get_kind () != other.get_kind ())
+      return false;
+    /* Call vfunc now we know they have the same ID: */
+    return subclass_equal_p (other);
+  }
+  /* A vfunc for testing for equality, where we've already
+     checked they have the same ID.  See pending_diagnostic_subclass
+     below for a convenience subclass for implementing this.  */
+  virtual bool subclass_equal_p (const pending_diagnostic &other) const = 0;
+
   /* For greatest precision-of-wording, give the pending diagnostic
      a chance to describe critical state changes in terms of the
      diagnostic.
@@ -176,6 +196,23 @@ class pending_diagnostic
     ATTRIBUTE_GCC_DIAG(1,2);
 };
 
+/* Use of the curiously-recurring template pattern, to implement
+   subclass_equal_p by casting and calling the operator==
+   This assumes that BASE_OTHER has already been checked to have
+   been of the same subclass.  */
+
+template <class Subclass>
+class pending_diagnostic_subclass : public pending_diagnostic
+{
+ public:
+  bool subclass_equal_p (const pending_diagnostic &base_other) const
+    FINAL OVERRIDE
+  {
+    const Subclass &other = (const Subclass &)base_other;
+    return *(const Subclass*)this == other;
+  }
+};
+
 #if 0
 /* A thing for state machines to pass to the engine to create
    pending_diagnostic instances (so that we can filter without having 
diff --git a/gcc/analyzer/impl-4.cc b/gcc/analyzer/impl-4.cc
index a5b4e6d..e7acb72 100644
--- a/gcc/analyzer/impl-4.cc
+++ b/gcc/analyzer/impl-4.cc
@@ -105,6 +105,7 @@ namespace impl_4 {
 class exploded_graph;
 class exploded_node;
 class exploded_edge;
+class exploded_path;
 class checker4_path;
 class stmt_finder;
 class state_purge_per_ssa_name;
@@ -829,7 +830,7 @@ struct stats
 
 /* FIXME.  */
 
-struct eg_hashmap_traits
+struct eg_hash_map_traits
 {
   typedef const point_and_state *key_type;
   typedef exploded_node *value_type;
@@ -883,6 +884,18 @@ public:
 		    pending_diagnostic *d);
   ~saved_diagnostic ();
 
+  bool operator== (const saved_diagnostic &other) const
+  {
+    return (&m_sm == &other.m_sm
+	    /* We don't compare m_enode.  */
+	    && m_node == other.m_node
+	    && m_stmt == other.m_stmt
+	    /* We don't compare m_stmt_finder.  */
+	    && m_var == other.m_var
+	    && m_state == other.m_state
+	    && m_d->equal_p (*other.m_d));
+  }
+
   //private:
   const state_machine &m_sm;
   const exploded_node *m_enode;
@@ -895,14 +908,13 @@ public:
 };
 
 /* A class with responsibility for saving pending diagnostics, so that
-   they can be emitted once the exploded_graph is complete (and the
-   shortest path found for each, potentially using edges that might
-   not have been found when the diagnostic was first saved).
+   they can be emitted after the exploded_graph is complete.
+   This lets us de-duplicate diagnostics, and find the shortest path
+   for each similar diagnostic, potentially using edges that might
+   not have been found when each diagnostic was first saved.
 
    This also lets us compute shortest_paths once, rather than
-   per-diagnostic.
-
-   Potentially could also do de-duplication of similar diagnostics.  */
+   per-diagnostic.  */
 
 class diagnostic_manager : public log_user
 {
@@ -918,8 +930,10 @@ public:
 
   void emit_saved_diagnostics (const exploded_graph &eg);
   void emit_saved_diagnostic (const exploded_graph &eg,
-			      const shortest_paths &sp,
-			      const saved_diagnostic &sd);
+			      const saved_diagnostic &sd,
+			      const exploded_path &epath,
+			      const gimple *stmt,
+			      int num_dupes);
 
   void prune_path (checker4_path *path,
 		   const state_machine &sm,
@@ -985,7 +999,7 @@ private:
      *pointers* as keys, rather than point_and_state, using the
      instance from within the exploded_node, with a custom hasher.  */
   typedef hash_map <const point_and_state *, exploded_node *,
-		    eg_hashmap_traits> map_t;
+		    eg_hash_map_traits> map_t;
   map_t m_point_and_state_to_node;
 
   auto_vec<exploded_node *> m_worklist;
@@ -1014,6 +1028,8 @@ public:
   exploded_path (const exploded_path &other);
   exploded_path & operator= (const exploded_path &other);
 
+  unsigned length () const { return m_edges.length (); }
+
   bool find_stmt_backwards (const gimple *search_stmt,
 			    int *out_idx) const;
 
@@ -3357,19 +3373,232 @@ diagnostic_manager::add_diagnostic (const state_machine &sm,
 				    pending_diagnostic *d)
 {
   // TODO: potentially do them directly, vs saved
-  // FIXME: stmt_finder might be on the stack!
   saved_diagnostic *sd
     = new saved_diagnostic (sm, enode, node, stmt, finder, var, state, d);
   m_saved_diagnostics.safe_push (sd);
 }
 
 /* FIXME.  */
+/* we want to find the simplest dedupe_candidate amongst those that share a
+   dedupe_key.  */
+
+class dedupe_key
+{
+public:
+  dedupe_key (const saved_diagnostic &sd,
+	      const exploded_path &epath)
+  : m_sd (sd), m_stmt (sd.m_stmt)
+  {
+    /* Support deferring the choice of stmt until after an emission path has
+     been built, using an optional stmt_finder.  */
+    if (m_stmt == NULL)
+      {
+	gcc_assert (sd.m_stmt_finder);
+	m_stmt = sd.m_stmt_finder->find_stmt (epath);
+      }
+    gcc_assert (m_stmt);
+  }
+
+  hashval_t hash () const
+  {
+    inchash::hash hstate;
+    hstate.add_ptr (m_stmt);
+    // TODO: m_sd
+    return hstate.end ();
+  }
+  bool operator== (const dedupe_key &other) const
+  {
+    return (m_sd == other.m_sd
+	    && m_stmt == other.m_stmt);
+  }
+
+  location_t get_location () const
+  {
+    return m_stmt->location;
+  }
+
+  /* A qsort comparator for use by dedupe_winners::emit_best
+     to sort them into location_t order.  */
+
+  static int
+  comparator (const void *p1, const void *p2)
+  {
+    const dedupe_key *pk1 = *(const dedupe_key * const *)p1;
+    const dedupe_key *pk2 = *(const dedupe_key * const *)p2;
+
+    location_t loc1 = pk1->get_location ();
+    location_t loc2 = pk2->get_location ();
+
+    return linemap_compare_locations (line_table, loc2, loc1);
+  }
+
+  // the pending_diagnostic
+  // the (stmt (non-NULL), var, state)
+  const saved_diagnostic &m_sd;
+  const gimple *m_stmt;
+};
+
+/* FIXME.  */
+class dedupe_candidate
+{
+public:
+  // has the exploded_path
+  dedupe_candidate  (const shortest_paths &sp,
+		     const saved_diagnostic &sd)
+  : m_epath (sp.get_shortest_path (sd.m_enode)),
+    m_num_dupes (0)
+  {
+  }
+
+  unsigned length () const { return m_epath.length (); }
+  const exploded_path &get_path () const { return m_epath; }
+
+private:
+  exploded_path m_epath;
+public:
+  int m_num_dupes;
+};
+
+/* FIXME.  */
+
+class dedupe_hash_map_traits
+{
+public:
+  typedef const dedupe_key *key_type;
+  typedef dedupe_candidate *value_type;
+  typedef dedupe_candidate *compare_type;
+
+  static inline hashval_t hash (const key_type &v)
+  {
+    return v->hash ();
+  }
+  static inline bool equal_keys (const key_type &k1, const key_type &k2)
+  {
+    return *k1 == *k2;
+  }
+  template <typename T>
+  static inline void remove (T &)
+  {
+    // TODO
+  }
+  template <typename T>
+  static inline void mark_deleted (T &entry)
+  {
+    entry.m_key = reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline void mark_empty (T &entry)
+  {
+    entry.m_key = NULL;
+  }
+  template <typename T>
+  static inline bool is_deleted (const T &entry)
+  {
+    return entry.m_key == reinterpret_cast<key_type> (1);
+  }
+  template <typename T>
+  static inline bool is_empty (const T &entry)
+  {
+    return entry.m_key == NULL;
+  }
+
+};
+
+/* A map from dedupe_key to best current dedupe_candidate.  */
+
+class dedupe_winners
+{
+public:
+  ~dedupe_winners ()
+  {
+    /* Delete all keys and candidates.  */
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      {
+	delete (*iter).first;
+	delete (*iter).second;
+      }
+  }
+
+  void add (const shortest_paths &sp,
+	    const saved_diagnostic &sd)
+  {
+    dedupe_candidate *dc = new dedupe_candidate (sp, sd);
+    dedupe_key *key = new dedupe_key (sd, dc->get_path ());
+    if (dedupe_candidate **slot = m_map.get (key))
+      {
+	(*slot)->m_num_dupes++;
+
+	if (dc->length () < (*slot)->length ())
+	  {
+	    /* We've got a shorter path for the key; replace
+	       the current candidate.  */
+	    dc->m_num_dupes = (*slot)->m_num_dupes;
+	    delete *slot;
+	    *slot = dc;
+	  }
+	else
+	  /* We haven't beaten the current best candidate;
+	     drop the new candidate.  */
+	  delete dc;
+	delete key;
+      }
+    else
+      /* This is the first candidate for this key.  */
+      m_map.put (key, dc);
+  }
+
+ /* Emit the simplest diagnostic within each set.  */
+
+  void emit_best (diagnostic_manager *dm,
+		  const exploded_graph &eg,
+		  const shortest_paths &sp)
+  {
+    JIT_LOG_SCOPE (dm->get_logger ());
+
+    /* Get keys into a vec for sorting.  */
+    auto_vec<const dedupe_key *> keys;
+    for (map_t::iterator iter = m_map.begin ();
+	 iter != m_map.end ();
+	 ++iter)
+      keys.safe_push ((*iter).first);
+
+    dm->log ("# keys after de-duplication: %i", keys.length ());
+
+    /* Sort into a good emission order.  */
+    keys.qsort (dedupe_key::comparator);
+
+    /* Emit the best candidate for each key.  */
+    int i;
+    const dedupe_key *key;
+    FOR_EACH_VEC_ELT (keys, i, key)
+      {
+	dedupe_candidate **slot = m_map.get (key);
+	gcc_assert (*slot);
+	const dedupe_candidate &dc = **slot;
+
+	dm->emit_saved_diagnostic (eg, key->m_sd,
+				   dc.get_path (), key->m_stmt,
+				   dc.m_num_dupes);
+      }
+  }
+
+private:
+
+  typedef hash_map<const dedupe_key *, dedupe_candidate *,
+		   dedupe_hash_map_traits> map_t;
+  map_t m_map;
+};
+
+/* FIXME.  */
 
 void
 diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
 {
   JIT_LOG_SCOPE (get_logger ());
   auto_client_timevar tv ("diagnostic_manager::emit_saved_diagnostics");
+  log ("# saved diagnostics: %i", m_saved_diagnostics.length ());
 
   if (m_saved_diagnostics.length () == 0)
     return;
@@ -3377,21 +3606,46 @@ diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
   /* Compute the shortest_paths once, sharing it between all diagnostics.  */
   shortest_paths sp (eg);
 
+  // FIXME: implement a way to disable de-duplication
+  /* FIXME: add stats on de-duplication, and a way to show the
+     number of duplicates (e.g. via an option that adds a message.
+     Add a DejaGnu test for this  */
+
+  // FIXME: this isn't true:
+  /* De-duplicate the diagnostics by generating a collection of diagnostic_set,
+     picking the diagnostic with the shortest path within each set.  */
+#if 1
+  dedupe_winners best_candidates;
+
+  int i;
+  saved_diagnostic *sd;
+  FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
+    best_candidates.add (sp, *sd);
+
+  best_candidates.emit_best (this, eg, sp);
+
+#else
+  // TODO: for now, just emit them all
+
   int i;
   saved_diagnostic *sd;
   FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
     emit_saved_diagnostic (eg, sp, *sd);
+#endif
 }
 
 /* FIXME.  */
 
 void
 diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
-					   const shortest_paths &sp,
-					   const saved_diagnostic &sd)
+					   const saved_diagnostic &sd,
+					   const exploded_path &epath,
+					   const gimple *stmt,
+					   int num_dupes)
 {
-  /* Generate a path.  */
-  exploded_path epath (sp.get_shortest_path (sd.m_enode));
+  JIT_LOG_SCOPE (get_logger ());
+  log ("num dupes: %i", num_dupes);
+
   pretty_printer *pp = global_dc->printer->clone ();
 
   checker4_path emission_path;
@@ -3528,16 +3782,6 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
 	}
     }
 
-  /* Support deferring the choice of stmt until after an emission path has
-     been built, using an optional stmt_finder.  */
-  const gimple *stmt = sd.m_stmt;
-  if (stmt == NULL)
-    {
-      gcc_assert (sd.m_stmt_finder);
-      stmt = sd.m_stmt_finder->find_stmt (epath);
-    }
-  gcc_assert (stmt);
-
   prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state);
   emission_path.add_final_event (sd.m_sm, sd.m_enode, stmt,
 				 sd.m_var, sd.m_state, sd.m_d);
@@ -3546,8 +3790,13 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
   gcc_rich_location rich_loc (stmt->location);
   rich_loc.set_path (&emission_path);
 
+  auto_diagnostic_group d;
   auto_cfun sentinel (sd.m_node->m_fun);
   sd.m_d->emit (&rich_loc);
+  if (num_dupes > 0)
+    inform_n (stmt->location, num_dupes,
+	      "%i duplicate", "%i duplicates",
+	      num_dupes);
   delete pp;
 }
 
@@ -3899,6 +4148,7 @@ exploded_graph::add_edge (exploded_node *src, exploded_node *dest,
 void
 exploded_graph::process_worklist ()
 {
+  JIT_LOG_SCOPE (get_logger ());
   auto_client_timevar tv ("exploded_graph::process_worklist");
 
   while (m_worklist.length () > 0)
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
index 75514ab..ba66e77 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-dedupe-1.c
@@ -3,12 +3,24 @@
 extern void foo (void);
 extern void bar (void);
 
-void test_1 (int flag)
+void test_1 (int flag, int n);
+
+void caller_1_of_test_1 (int n)
+{
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
+}
+
+void test_1 (int flag, int n)
 {
   int *ptr = (int *)malloc (sizeof (int));
 
   if (flag)
-    foo ();
+    {
+      int i;
+      for (i = 0; i < n; i++)
+	foo ();
+    }
   else
     bar ();
 
@@ -18,7 +30,7 @@ void test_1 (int flag)
      through the function.
      The calls in test_2 also generate additional duplicates.
      How to verify lack of duplicates?
-     Maybe a bogus on the interprocedual one?  */
+     Putting a bogus on the interprocedual one detects that, at least.  */
 
   if (flag)
     foo ();
@@ -26,8 +38,8 @@ void test_1 (int flag)
     bar ();
 }
 
-void test_2 (void)
+void caller_2_of_test_1 (int n)
 {
-  test_1 (1);
-  test_1 (0);
+  test_1 (1, n); /* { dg-bogus "test_1" } */
+  test_1 (0, n); /* { dg-bogus "test_1" } */
 }
-- 
1.8.5.3

