diff mbox series

[pushed] analyzer: remove default return value from region_model::on_call_pre

Message ID 20230809202104.1804417-1-dmalcolm@redhat.com
State New
Headers show
Series [pushed] analyzer: remove default return value from region_model::on_call_pre | expand

Commit Message

David Malcolm Aug. 9, 2023, 8:21 p.m. UTC
Previously, the code for simulating calls to external functions in
region_model::on_call_pre wrote a default svalue to the LHS of the
call statement, which could be further overwritten by known_function
subclasses.

Unfortunately, this led to messy hacks, such as when the default svalue
was an allocation: the LHS would be written to with two different
heap-allocated regions, requiring special-case cleanups to avoid the
stray state from the first heap allocation leading to state explosions;
see r14-3001-g021077b94741c9.

The following patch eliminates this write of a default svalue to the LHS
of callsite.  Instead, all known_function implementations that have a
return value are now responsible for set the LHS themselves.  A new
call_details::set_any_lhs_with_defaults function is provided to make it
easy to get the old behavior.

On working through the various known_function subclasses, I noticed that
memset was using the default behavior.  That patch updates this so that
it's now known to return its first parameter.

Cleaning this up eliminates various doubling of saved_diagnostics (e.g.
for dubious_allocation_size) where it was generating a diagnostic for
both writes to the LHS, deduplicating them to the first diagnostic (with
the default LHS), and then failing to create a region_creation_event
when emitting the diagnostic, leading to the fallback wording in
dubious_allocation_size::describe_final_event, such as:

  (1) allocated 42 bytes and assigned to ‘int32_t *’ {aka ‘int *’} here; ‘sizeof (int32_t {aka int})’ is ‘4’

Without the double write to the LHS, it creates a region_creation_event,
so we get the allocation and the assignment as two separate events in
the diagnostic path, e.g.:

  (1) allocated 42 bytes here
  (2) assigned to ‘int32_t *’ {aka ‘int *’} here; ‘sizeof (int32_t {aka int})’ is ‘4’

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r14-3114-g73da34a538ddc2.

gcc/analyzer/ChangeLog:
	* analyzer.h (class pure_known_function_with_default_return): New
	subclass.
	* call-details.cc (const_fn_p): Move here from region-model.cc.
	(maybe_get_const_fn_result): Likewise.
	(get_result_size_in_bytes): Likewise.
	(call_details::set_any_lhs_with_defaults): New function, based on
	code in region_model::on_call_pre.
	* call-details.h (call_details::set_any_lhs_with_defaults): New
	decl.
	* diagnostic-manager.cc
	(diagnostic_manager::emit_saved_diagnostic): Log the index of the
	saved_diagnostic.
	* kf.cc (pure_known_function_with_default_return::impl_call_pre):
	New.
	(kf_memset::impl_call_pre): Set the LHS to the first param.
	(kf_putenv::impl_call_pre): Call cd.set_any_lhs_with_defaults.
	(kf_sprintf::impl_call_pre): Call cd.set_any_lhs_with_defaults.
	(class kf_stack_restore): Derive from
	pure_known_function_with_default_return.
	(class kf_stack_save): Likewise.
	(kf_strlen::impl_call_pre): Call cd.set_any_lhs_with_defaults.
	* region-model-reachability.cc (reachable_regions::handle_sval):
	Remove logic for symbolic regions for pointers.
	* region-model.cc (region_model::canonicalize): Remove purging of
	dynamic extents workaround for surplus values from
	region_model::on_call_pre's default LHS code.
	(const_fn_p): Move to call-details.cc.
	(maybe_get_const_fn_result): Likewise.
	(get_result_size_in_bytes): Likewise.
	(region_model::update_for_nonzero_return): Call
	cd.set_any_lhs_with_defaults.
	(region_model::on_call_pre): Remove the assignment to the LHS of a
	default return value, instead requiring all known_function
	implementations to write to any LHS of the call.  Use
	cd.set_any_lhs_with_defaults on the non-kf paths.
	* sm-fd.cc (kf_socket::outcome_of_socket::update_model): Use
	cd.set_any_lhs_with_defaults when failing to get at fd state.
	(kf_bind::outcome_of_bind::update_model): Likewise.
	(kf_listen::outcome_of_listen::update_model): Likewise.
	(kf_accept::outcome_of_accept::update_model): Likewise.
	(kf_connect::outcome_of_connect::update_model): Likewise.
	(kf_read::impl_call_pre): Use cd.set_any_lhs_with_defaults.
	* sm-file.cc (class kf_stdio_output_fn): Derive from
	pure_known_function_with_default_return.
	(class kf_ferror): Likewise.
	(class kf_fileno): Likewise.
	(kf_fgets::impl_call_pre): Use cd.set_any_lhs_with_defaults.
	(kf_read::impl_call_pre): Likewise.
	(class kf_getc): Derive from
	pure_known_function_with_default_return.
	(class kf_getchar): Likewise.
	* varargs.cc (kf_va_arg::impl_call_pre): Use
	cd.set_any_lhs_with_defaults.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/allocation-size-1.c: Update expected results
	to reflect splitting of allocation size and assignment messages
	from a single event into pairs of events
	* gcc.dg/analyzer/allocation-size-2.c: Likewise.
	* gcc.dg/analyzer/allocation-size-3.c: Likewise.
	* gcc.dg/analyzer/allocation-size-4.c: Likewise.
	* gcc.dg/analyzer/allocation-size-multiline-1.c: Likewise.
	* gcc.dg/analyzer/allocation-size-multiline-2.c: Likewise.
	* gcc.dg/analyzer/allocation-size-multiline-3.c: Likewise.
	* gcc.dg/analyzer/memset-1.c (test_1): Verify that the return
	value is the initial argument.
	* gcc.dg/plugin/analyzer_kernel_plugin.c
	(copy_across_boundary_fn::impl_call_pre): Ensure the LHS is set on
	the "known zero size" case.
	* gcc.dg/plugin/analyzer_known_fns_plugin.c
	(known_function_attempt_to_copy::impl_call_pre): Likewise.
---
 gcc/analyzer/analyzer.h                       |  10 ++
 gcc/analyzer/call-details.cc                  | 129 +++++++++++++++
 gcc/analyzer/call-details.h                   |   1 +
 gcc/analyzer/diagnostic-manager.cc            |   3 +-
 gcc/analyzer/kf.cc                            |  18 +-
 gcc/analyzer/region-model-reachability.cc     |  21 ---
 gcc/analyzer/region-model.cc                  | 156 ++----------------
 gcc/analyzer/sm-fd.cc                         |  51 ++++--
 gcc/analyzer/sm-file.cc                       |  14 +-
 gcc/analyzer/varargs.cc                       |   2 +
 .../gcc.dg/analyzer/allocation-size-1.c       |   3 +-
 .../gcc.dg/analyzer/allocation-size-2.c       |   3 +-
 .../gcc.dg/analyzer/allocation-size-3.c       |   9 +-
 .../gcc.dg/analyzer/allocation-size-4.c       |   6 +-
 .../analyzer/allocation-size-multiline-1.c    |  12 +-
 .../analyzer/allocation-size-multiline-2.c    |  15 +-
 .../analyzer/allocation-size-multiline-3.c    |  10 +-
 gcc/testsuite/gcc.dg/analyzer/memset-1.c      |   3 +-
 .../gcc.dg/plugin/analyzer_kernel_plugin.c    |   7 +-
 .../gcc.dg/plugin/analyzer_known_fns_plugin.c |   7 +-
 20 files changed, 266 insertions(+), 214 deletions(-)
diff mbox series

Patch

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 579517c23e6..93a28b4b5cf 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -293,6 +293,16 @@  public:
   }
 };
 
+/* Abstract subclass of known_function that merely sets the return
+   value of the function (based on function attributes), and assumes
+   it has no side-effects.  */
+
+class pure_known_function_with_default_return : public known_function
+{
+public:
+  void impl_call_pre (const call_details &cd) const override;
+};
+
 extern void register_known_functions (known_function_manager &mgr);
 extern void register_known_analyzer_functions (known_function_manager &kfm);
 extern void register_known_fd_functions (known_function_manager &kfm);
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index 793317eaa02..93f4846f674 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -105,6 +105,135 @@  call_details::maybe_set_lhs (const svalue *result) const
     return false;
 }
 
+/* Return true if CD is known to be a call to a function with
+   __attribute__((const)).  */
+
+static bool
+const_fn_p (const call_details &cd)
+{
+  tree fndecl = cd.get_fndecl_for_call ();
+  if (!fndecl)
+    return false;
+  gcc_assert (DECL_P (fndecl));
+  return TREE_READONLY (fndecl);
+}
+
+/* If this CD is known to be a call to a function with
+   __attribute__((const)), attempt to get a const_fn_result_svalue
+   based on the arguments, or return NULL otherwise.  */
+
+static const svalue *
+maybe_get_const_fn_result (const call_details &cd)
+{
+  if (!const_fn_p (cd))
+    return NULL;
+
+  unsigned num_args = cd.num_args ();
+  if (num_args > const_fn_result_svalue::MAX_INPUTS)
+    /* Too many arguments.  */
+    return NULL;
+
+  auto_vec<const svalue *> inputs (num_args);
+  for (unsigned arg_idx = 0; arg_idx < num_args; arg_idx++)
+    {
+      const svalue *arg_sval = cd.get_arg_svalue (arg_idx);
+      if (!arg_sval->can_have_associated_state_p ())
+	return NULL;
+      inputs.quick_push (arg_sval);
+    }
+
+  region_model_manager *mgr = cd.get_manager ();
+  const svalue *sval
+    = mgr->get_or_create_const_fn_result_svalue (cd.get_lhs_type (),
+						 cd.get_fndecl_for_call (),
+						 inputs);
+  return sval;
+}
+
+/* Look for attribute "alloc_size" on the called function and, if found,
+   return a symbolic value of type size_type_node for the allocation size
+   based on the call's parameters.
+   Otherwise, return null.  */
+
+static const svalue *
+get_result_size_in_bytes (const call_details &cd)
+{
+  const tree attr = cd.lookup_function_attribute ("alloc_size");
+  if (!attr)
+    return nullptr;
+
+  const tree atval_1 = TREE_VALUE (attr);
+  if (!atval_1)
+    return nullptr;
+
+  unsigned argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval_1)) - 1;
+  if (cd.num_args () <= argidx1)
+    return nullptr;
+
+  const svalue *sval_arg1 = cd.get_arg_svalue (argidx1);
+
+  if (const tree atval_2 = TREE_CHAIN (atval_1))
+    {
+      /* Two arguments.  */
+      unsigned argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval_2)) - 1;
+      if (cd.num_args () <= argidx2)
+	return nullptr;
+      const svalue *sval_arg2 = cd.get_arg_svalue (argidx2);
+      /* TODO: ideally we shouldn't need this cast here;
+	 see PR analyzer/110902.  */
+      return cd.get_manager ()->get_or_create_cast
+	(size_type_node,
+	 cd.get_manager ()->get_or_create_binop (size_type_node,
+						 MULT_EXPR,
+						 sval_arg1, sval_arg2));
+    }
+  else
+    /* Single argument.  */
+    return cd.get_manager ()->get_or_create_cast (size_type_node, sval_arg1);
+}
+
+/* If this call has an LHS, assign a value to it based on attributes
+   of the function:
+   - if __attribute__((const)), use a const_fn_result_svalue,
+   - if __attribute__((malloc)), use a heap-allocated region with
+   unknown content
+   - otherwise, use a conjured_svalue.
+
+   If __attribute__((alloc_size), set the dynamic extents on the region
+   pointed to.  */
+
+void
+call_details::set_any_lhs_with_defaults () const
+{
+  if (!m_lhs_region)
+    return;
+
+  const svalue *sval = maybe_get_const_fn_result (*this);
+  if (!sval)
+    {
+      region_model_manager *mgr = get_manager ();
+      if (lookup_function_attribute ("malloc"))
+	{
+	  const region *new_reg
+	    = m_model->get_or_create_region_for_heap_alloc (NULL, m_ctxt);
+	  m_model->mark_region_as_unknown (new_reg, NULL);
+	  sval = mgr->get_ptr_svalue (get_lhs_type (), new_reg);
+	}
+      else
+	/* For the common case of functions without __attribute__((const)),
+	   use a conjured value, and purge any prior state involving that
+	   value (in case this is in a loop).  */
+	sval = get_or_create_conjured_svalue (m_lhs_region);
+      if (const svalue *size_in_bytes = get_result_size_in_bytes (*this))
+	{
+	  const region *reg
+	    = m_model->deref_rvalue (sval, NULL_TREE, m_ctxt, false);
+	  m_model->set_dynamic_extents (reg, size_in_bytes, m_ctxt);
+	}
+    }
+  maybe_set_lhs (sval);
+}
+
 /* Return the number of arguments used by the call statement.  */
 
 unsigned
diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h
index 25ea5546182..24be2247e63 100644
--- a/gcc/analyzer/call-details.h
+++ b/gcc/analyzer/call-details.h
@@ -41,6 +41,7 @@  public:
   const region *get_lhs_region () const { return m_lhs_region; }
 
   bool maybe_set_lhs (const svalue *result) const;
+  void set_any_lhs_with_defaults () const;
 
   unsigned num_args () const;
   bool arg_is_pointer_p (unsigned idx) const
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index cfca305d552..8bc84c82055 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1372,7 +1372,8 @@  diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
 					   const saved_diagnostic &sd)
 {
   LOG_SCOPE (get_logger ());
-  log ("sd: %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index);
+  log ("sd[%i]: %qs at SN: %i",
+       sd.get_index (), sd.m_d->get_kind (), sd.m_snode->m_index);
   log ("num dupes: %i", sd.get_num_dupes ());
 
   pretty_printer *pp = global_dc->printer->clone ();
diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index 3e319a076bb..b9ee2e45c86 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -40,6 +40,15 @@  along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+/* class pure_known_function_with_default_return : public known_function.  */
+
+void
+pure_known_function_with_default_return::
+impl_call_pre (const call_details &cd) const
+{
+  cd.set_any_lhs_with_defaults ();
+}
+
 /* Implementations of specific functions.  */
 
 /* Handler for "alloca".  */
@@ -557,6 +566,8 @@  kf_memset::impl_call_pre (const call_details &cd) const
 				 nullptr,
 				 cd.get_ctxt ());
   model->fill_region (sized_dest_reg, fill_value_u8);
+
+  cd.maybe_set_lhs (dest_sval);
 }
 
 /* A subclass of pending_diagnostic for complaining about 'putenv'
@@ -683,6 +694,7 @@  public:
 	  ctxt->warn (make_unique<putenv_of_auto_var> (fndecl, reg));
 	break;
       }
+    cd.set_any_lhs_with_defaults ();
   }
 };
 
@@ -1034,12 +1046,13 @@  public:
       = model->deref_rvalue (dst_ptr, cd.get_arg_tree (0), ctxt);
     const svalue *content = cd.get_or_create_conjured_svalue (dst_reg);
     model->set_value (dst_reg, content, ctxt);
+    cd.set_any_lhs_with_defaults ();
   }
 };
 
 /* Handler for "__builtin_stack_restore".  */
 
-class kf_stack_restore : public known_function
+class kf_stack_restore : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &) const final override
@@ -1052,7 +1065,7 @@  public:
 
 /* Handler for "__builtin_stack_save".  */
 
-class kf_stack_save : public known_function
+class kf_stack_save : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &) const final override
@@ -1175,6 +1188,7 @@  kf_strlen::impl_call_pre (const call_details &cd) const
 	}
     }
   /* Otherwise a conjured value.  */
+  cd.set_any_lhs_with_defaults ();
 }
 
 /* Handler for "strndup" and "__builtin_strndup".  */
diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc
index 1c747e14eab..a5c12f49346 100644
--- a/gcc/analyzer/region-model-reachability.cc
+++ b/gcc/analyzer/region-model-reachability.cc
@@ -184,27 +184,6 @@  reachable_regions::handle_sval (const svalue *sval)
 	}
       add (pointee, ptr_is_mutable);
     }
-  else if (sval->get_type ()
-	   && TREE_CODE (sval->get_type ()) == POINTER_TYPE
-	   && sval->get_kind () == SK_CONJURED)
-    {
-      /* Also add symbolic regions for pointers, but only for conjured svalues
-	 for the LHS of a stmt.  Doing it for more leads to state explosions
-	 on chains of calls to external functions, due to each conjured svalue
-	 potentially being modified at each successive call, recursively.  */
-      const conjured_svalue *conjured_sval = (const conjured_svalue *)sval;
-      if (conjured_sval->lhs_value_p ())
-	{
-	  const region *pointee
-	    = m_model->get_manager ()->get_symbolic_region (sval);
-	  /* Use const-ness of pointer type to affect mutability.  */
-	  bool ptr_is_mutable = true;
-	  if (TYPE_READONLY (TREE_TYPE (sval->get_type ())))
-	    ptr_is_mutable = false;
-	  add (pointee, ptr_is_mutable);
-	}
-    }
-
   /* Treat all svalues within a compound_svalue as reachable.  */
   if (const compound_svalue *compound_sval
       = sval->dyn_cast_compound_svalue ())
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index e92b3f7b074..094b7af3dbc 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -441,29 +441,6 @@  region_model::canonicalize ()
 {
   m_store.canonicalize (m_mgr->get_store_manager ());
   m_constraints->canonicalize ();
-
-  if (!m_dynamic_extents.is_empty ())
-    {
-      /* Purge any dynamic extents for regions that aren't referenced.
-	 Normally these are eliminated when leaks are detected, but we
-	 can also gain stray heap_allocated_regions that aren't seen
-	 by the leak-detection code.  This happens when
-	 region_model::on_call_pre provides a default result for a
-	 function with both attributes "malloc" and "alloc_size" that
-	 also has a known_function implementation.
-	 Purge dynamic extent information for such regions.  */
-      auto_bitmap referenced_base_region_ids;
-      get_referenced_base_regions (referenced_base_region_ids);
-      auto_vec<const region *> purgable_dyn_extents;
-      for (auto iter : m_dynamic_extents)
-	{
-	  const region *reg = iter.first;
-	  if (!bitmap_bit_p (referenced_base_region_ids, reg->get_id ()))
-	    purgable_dyn_extents.safe_push (reg);
-	}
-      for (auto reg : purgable_dyn_extents)
-	m_dynamic_extents.remove (reg);
-    }
 }
 
 /* Return true if this region_model is in canonical form.  */
@@ -1304,51 +1281,6 @@  region_model::check_call_args (const call_details &cd) const
     cd.get_arg_svalue (arg_idx);
 }
 
-/* Return true if CD is known to be a call to a function with
-   __attribute__((const)).  */
-
-static bool
-const_fn_p (const call_details &cd)
-{
-  tree fndecl = cd.get_fndecl_for_call ();
-  if (!fndecl)
-    return false;
-  gcc_assert (DECL_P (fndecl));
-  return TREE_READONLY (fndecl);
-}
-
-/* If this CD is known to be a call to a function with
-   __attribute__((const)), attempt to get a const_fn_result_svalue
-   based on the arguments, or return NULL otherwise.  */
-
-static const svalue *
-maybe_get_const_fn_result (const call_details &cd)
-{
-  if (!const_fn_p (cd))
-    return NULL;
-
-  unsigned num_args = cd.num_args ();
-  if (num_args > const_fn_result_svalue::MAX_INPUTS)
-    /* Too many arguments.  */
-    return NULL;
-
-  auto_vec<const svalue *> inputs (num_args);
-  for (unsigned arg_idx = 0; arg_idx < num_args; arg_idx++)
-    {
-      const svalue *arg_sval = cd.get_arg_svalue (arg_idx);
-      if (!arg_sval->can_have_associated_state_p ())
-	return NULL;
-      inputs.quick_push (arg_sval);
-    }
-
-  region_model_manager *mgr = cd.get_manager ();
-  const svalue *sval
-    = mgr->get_or_create_const_fn_result_svalue (cd.get_lhs_type (),
-						 cd.get_fndecl_for_call (),
-						 inputs);
-  return sval;
-}
-
 /* Update this model for an outcome of a call that returns a specific
    integer constant.
    If UNMERGEABLE, then make the result unmergeable, e.g. to prevent
@@ -1381,7 +1313,9 @@  region_model::update_for_zero_return (const call_details &cd,
   update_for_int_cst_return (cd, 0, unmergeable);
 }
 
-/* Update this model for an outcome of a call that returns non-zero.  */
+/* Update this model for an outcome of a call that returns non-zero.
+   Specifically, assign an svalue to the LHS, and add a constraint that
+   that svalue is non-zero.  */
 
 void
 region_model::update_for_nonzero_return (const call_details &cd)
@@ -1390,6 +1324,7 @@  region_model::update_for_nonzero_return (const call_details &cd)
     return;
   if (TREE_CODE (cd.get_lhs_type ()) != INTEGER_TYPE)
     return;
+  cd.set_any_lhs_with_defaults ();
   const svalue *zero
     = m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
   const svalue *result
@@ -1485,48 +1420,6 @@  region_model::get_known_function (enum internal_fn ifn) const
   return known_fn_mgr->get_internal_fn (ifn);
 }
 
-/* Look for attribute "alloc_size" on the called function and, if found,
-   return a symbolic value of type size_type_node for the allocation size
-   based on the call's parameters.
-   Otherwise, return null.  */
-
-static const svalue *
-get_result_size_in_bytes (const call_details &cd)
-{
-  const tree attr = cd.lookup_function_attribute ("alloc_size");
-  if (!attr)
-    return nullptr;
-
-  const tree atval_1 = TREE_VALUE (attr);
-  if (!atval_1)
-    return nullptr;
-
-  unsigned argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval_1)) - 1;
-  if (cd.num_args () <= argidx1)
-    return nullptr;
-
-  const svalue *sval_arg1 = cd.get_arg_svalue (argidx1);
-
-  if (const tree atval_2 = TREE_CHAIN (atval_1))
-    {
-      /* Two arguments.  */
-      unsigned argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval_2)) - 1;
-      if (cd.num_args () <= argidx2)
-	return nullptr;
-      const svalue *sval_arg2 = cd.get_arg_svalue (argidx2);
-      /* TODO: ideally we shouldn't need this cast here;
-	 see PR analyzer/110902.  */
-      return cd.get_manager ()->get_or_create_cast
-	(size_type_node,
-	 cd.get_manager ()->get_or_create_binop (size_type_node,
-						 MULT_EXPR,
-						 sval_arg1, sval_arg2));
-    }
-  else
-    /* Single argument.  */
-    return cd.get_manager ()->get_or_create_cast (size_type_node, sval_arg1);
-}
-
 /* Update this model for the CALL stmt, using CTXT to report any
    diagnostics - the first half.
 
@@ -1562,40 +1455,6 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
 
   tree callee_fndecl = get_fndecl_for_call (call, ctxt);
 
-  /* Some of the cases below update the lhs of the call based on the
-     return value, but not all.  Provide a default value, which may
-     get overwritten below.  */
-  if (tree lhs = gimple_call_lhs (call))
-    {
-      const region *lhs_region = get_lvalue (lhs, ctxt);
-      const svalue *sval = maybe_get_const_fn_result (cd);
-      if (!sval)
-	{
-	  if (callee_fndecl
-	      && lookup_attribute ("malloc", DECL_ATTRIBUTES (callee_fndecl)))
-	    {
-	      const region *new_reg
-		= get_or_create_region_for_heap_alloc (NULL, ctxt);
-	      mark_region_as_unknown (new_reg, NULL);
-	      sval = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
-	    }
-	  else
-	    /* For the common case of functions without __attribute__((const)),
-	       use a conjured value, and purge any prior state involving that
-	       value (in case this is in a loop).  */
-	    sval = m_mgr->get_or_create_conjured_svalue (TREE_TYPE (lhs), call,
-							 lhs_region,
-							 conjured_purge (this,
-									 ctxt));
-	  if (const svalue *size_in_bytes = get_result_size_in_bytes (cd))
-	    {
-	      const region *reg = deref_rvalue (sval, NULL_TREE, ctxt, false);
-	      set_dynamic_extents (reg, size_in_bytes, ctxt);
-	    }
-	}
-      set_value (lhs_region, sval, ctxt);
-    }
-
   if (gimple_call_internal_p (call))
     if (const known_function *kf
 	  = get_known_function (gimple_call_internal_fn (call)))
@@ -1605,7 +1464,10 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
       }
 
   if (!callee_fndecl)
-    return true; /* Unknown side effects.  */
+    {
+      cd.set_any_lhs_with_defaults ();
+      return true; /* Unknown side effects.  */
+    }
 
   if (const known_function *kf = get_known_function (callee_fndecl, cd))
     {
@@ -1613,6 +1475,8 @@  region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
       return false; /* No further side effects.  */
     }
 
+  cd.set_any_lhs_with_defaults ();
+
   const int callee_fndecl_flags = flags_from_decl_or_type (callee_fndecl);
   if (callee_fndecl_flags & (ECF_CONST | ECF_PURE))
     return false; /* No side effects.  */
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index 03ad3598a3c..c75744ff63a 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -2282,10 +2282,16 @@  public:
       const fd_state_machine *fd_sm;
       std::unique_ptr<sm_context> sm_ctxt;
       if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, &sm_ctxt))
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       const extrinsic_state *ext_state = ctxt->get_ext_state ();
       if (!ext_state)
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
 
       return fd_sm->on_socket (cd, m_success, sm_ctxt.get (), *ext_state);
     }
@@ -2329,10 +2335,16 @@  public:
       const fd_state_machine *fd_sm;
       std::unique_ptr<sm_context> sm_ctxt;
       if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, &sm_ctxt))
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       const extrinsic_state *ext_state = ctxt->get_ext_state ();
       if (!ext_state)
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       return fd_sm->on_bind (cd, m_success, sm_ctxt.get (), *ext_state);
     }
   };
@@ -2374,10 +2386,16 @@  class kf_listen : public known_function
       const fd_state_machine *fd_sm;
       std::unique_ptr<sm_context> sm_ctxt;
       if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, &sm_ctxt))
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       const extrinsic_state *ext_state = ctxt->get_ext_state ();
       if (!ext_state)
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
 
       return fd_sm->on_listen (cd, m_success, sm_ctxt.get (), *ext_state);
     }
@@ -2420,10 +2438,16 @@  class kf_accept : public known_function
       const fd_state_machine *fd_sm;
       std::unique_ptr<sm_context> sm_ctxt;
       if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, &sm_ctxt))
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       const extrinsic_state *ext_state = ctxt->get_ext_state ();
       if (!ext_state)
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
 
       return fd_sm->on_accept (cd, m_success, sm_ctxt.get (), *ext_state);
     }
@@ -2469,10 +2493,16 @@  public:
       const fd_state_machine *fd_sm;
       std::unique_ptr<sm_context> sm_ctxt;
       if (!get_fd_state (ctxt, &smap, &fd_sm, NULL, &sm_ctxt))
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
       const extrinsic_state *ext_state = ctxt->get_ext_state ();
       if (!ext_state)
-	return true;
+	{
+	  cd.set_any_lhs_with_defaults ();
+	  return true;
+	}
 
       return fd_sm->on_connect (cd, m_success, sm_ctxt.get (), *ext_state);
     }
@@ -2687,6 +2717,7 @@  public:
 	const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
 	model->set_value (base_reg, new_sval, cd.get_ctxt ());
       }
+    cd.set_any_lhs_with_defaults ();
   }
 };
 
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index 0cfe6821722..0252b3922d4 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -494,7 +494,7 @@  make_fileptr_state_machine (logger *logger)
    effects that are out of scope for the analyzer: we only want to model
    the effects on the return value.  */
 
-class kf_stdio_output_fn : public known_function
+class kf_stdio_output_fn : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &) const final override
@@ -507,7 +507,7 @@  public:
 
 /* Handler for "ferror"".  */
 
-class kf_ferror : public known_function
+class kf_ferror : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &cd) const final override
@@ -521,7 +521,7 @@  public:
 
 /* Handler for "fileno"".  */
 
-class kf_fileno : public known_function
+class kf_fileno : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &cd) const final override
@@ -557,6 +557,7 @@  public:
 	const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
 	model->set_value (base_reg, new_sval, cd.get_ctxt ());
       }
+    cd.set_any_lhs_with_defaults ();
   }
 };
 
@@ -592,12 +593,13 @@  public:
 	const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
 	model->set_value (base_reg, new_sval, cd.get_ctxt ());
       }
+    cd.set_any_lhs_with_defaults ();
   }
 };
 
 /* Handler for "getc"".  */
 
-class kf_getc : public known_function
+class kf_getc : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &cd) const final override
@@ -605,13 +607,11 @@  public:
     return (cd.num_args () == 1
 	    && cd.arg_is_pointer_p (0));
   }
-
-  /* No side effects.  */
 };
 
 /* Handler for "getchar"".  */
 
-class kf_getchar : public known_function
+class kf_getchar : public pure_known_function_with_default_return
 {
 public:
   bool matches_call_types_p (const call_details &cd) const final override
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index 72e1b31601c..f79b2a7d7b5 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -1007,6 +1007,8 @@  kf_va_arg::impl_call_pre (const call_details &cd) const
   tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0));
   ap_sval = model->check_for_poison (ap_sval, va_list_tree, ap_reg, ctxt);
 
+  cd.set_any_lhs_with_defaults ();
+
   if (const region *impl_reg = ap_sval->maybe_get_region ())
     {
       const svalue *old_impl_sval = model->get_store_value (impl_reg, ctxt);
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
index dcffc1175cf..003914ed96c 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c
@@ -16,7 +16,8 @@  void test_2 (void)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */
-  /* { dg-message "allocated 42 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "42 bytes" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
 }
 
 void test_3 (void)
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
index d26c2672531..eb770f73d4a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c
@@ -19,7 +19,8 @@  void test_2 (int32_t n)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */
-  /* { dg-message "allocated '\[a-z0-9\\*\\(\\)\\s\]*' bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "'\[a-z0-9\\*\\(\\)\\s\]*' bytes" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4" "note" { target *-*-* } malloc2 } */
 }
 
 void test_3 (int32_t n)
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
index 6b753073008..6751441dd18 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c
@@ -20,7 +20,8 @@  void test_1 (void)
   free (id_sequence);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc1 } */
-  /* { dg-message "allocated 3 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc1 } */
+  /* { dg-message "3 bytes" "note" { target *-*-* } malloc1 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc1 } */
 }
 
 void test_2 (void)
@@ -29,7 +30,8 @@  void test_2 (void)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */
-  /* { dg-message "allocated 14 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "14 bytes" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
 }
 
 void test_3 (int32_t n)
@@ -38,7 +40,8 @@  void test_3 (int32_t n)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc3 } */
-  /* { dg-message "allocated '\[a-z0-9\\+\\(\\)\\s\]*' bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc3 } */
+  /* { dg-message "'\[a-z0-9\\+\\(\\)\\s\]*' bytes" "note" { target *-*-* } malloc3 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc3 } */
 }
 
 void test_4 (int32_t n, int32_t m)
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
index 642e8f5f496..a56b25b4374 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c
@@ -30,7 +30,8 @@  void test_2 (void)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */
-  /* { dg-message "allocated \\d+ bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "\\d+ bytes" "note" { target *-*-* } malloc2 } */
+  /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */
 }
 
 void test_3 (void)
@@ -55,5 +56,6 @@  void test_5 (void)
   free (ptr);
 
   /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc5 } */
-  /* { dg-message "allocated 1 bytes and assigned to 'struct base \\*' here; 'sizeof \\(struct base\\)' is '\\d+'" "note" { target *-*-* } malloc5 } */
+  /* { dg-message "allocated 1 byte here" "note" { target *-*-* } malloc5 } */
+  /* { dg-message "'struct base \\*' here; 'sizeof \\(struct base\\)' is '\\d+'" "note" { target *-*-* } malloc5 } */
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c
index 9938ba237a0..7251665105d 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c
@@ -11,12 +11,13 @@  void test_constant_1 (void)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = __builtin_malloc (1);
                   ^~~~~~~~~~~~~~~~~~~~
-  'test_constant_1': event 1
+  'test_constant_1': events 1-2
     |
     |   int32_t *ptr = __builtin_malloc (1);
     |                  ^~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 1 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 1 byte here
+    |                  (2) assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
 
@@ -29,12 +30,13 @@  void test_constant_2 (void)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = __builtin_malloc (2);
                   ^~~~~~~~~~~~~~~~~~~~
-  'test_constant_2': event 1
+  'test_constant_2': events 1-2
     |
     |   int32_t *ptr = __builtin_malloc (2);
     |                  ^~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 2 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 2 bytes here
+    |                  (2) assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
 
@@ -52,6 +54,6 @@  void test_symbolic (int n)
     |   int32_t *ptr = __builtin_malloc (n * 2);
     |                  ^~~~~~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 'n * 2' bytes and assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c
index 9e1269cbb7a..7cadbb74751 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c
@@ -11,12 +11,13 @@  void test_constant_1 (void)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = __builtin_alloca (1);
                   ^~~~~~~~~~~~~~~~~~~~
-  'test_constant_1': event 1
+  'test_constant_1': events 1-2
     |
     |   int32_t *ptr = __builtin_alloca (1);
     |                  ^~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 1 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 1 byte here
+    |                  (2) assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
 
@@ -28,12 +29,13 @@  void test_constant_2 (void)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = __builtin_alloca (2);
                   ^~~~~~~~~~~~~~~~~~~~
-  'test_constant_2': event 1
+  'test_constant_2': events 1-2
     |
     |   int32_t *ptr = __builtin_alloca (2);
     |                  ^~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 2 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 2 bytes here
+    |                  (2) assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
 
@@ -45,12 +47,13 @@  void test_symbolic (int n)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = __builtin_alloca (n * 2);
                   ^~~~~~~~~~~~~~~~~~~~~~~~
-  'test_symbolic': event 1
+  'test_symbolic': events 1-2
     |
     |   int32_t *ptr = __builtin_alloca (n * 2);
     |                  ^~~~~~~~~~~~~~~~~~~~~~~~
     |                  |
-    |                  (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 'n * 2' bytes here
+    |                  (2) assigned to 'int32_t *'
     |
    { dg-end-multiline-output "" } */
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c
index 71790d91753..b3de582368f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c
+++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c
@@ -15,12 +15,13 @@  void test_constant_99 (void)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = alloca (99);
                   ^~~~~~
-  'test_constant_99': event 1
+  'test_constant_99': events 1-2
     |
     |   int32_t *ptr = alloca (99);
     |                  ^~~~~~
     |                  |
-    |                  (1) allocated 99 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 99 bytes here
+    |                  (2) assigned to 'int32_t *' {aka '{re:long :re?}int *'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4'
     |
    { dg-end-multiline-output "" } */
 
@@ -32,11 +33,12 @@  void test_symbolic (int n)
 /* { dg-begin-multiline-output "" }
    int32_t *ptr = alloca (n * 2);
                   ^~~~~~
-  'test_symbolic': event 1
+  'test_symbolic': events 1-2
     |
     |   int32_t *ptr = alloca (n * 2);
     |                  ^~~~~~
     |                  |
-    |                  (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4'
+    |                  (1) allocated 'n * 2' bytes here
+    |                  (2) assigned to 'int32_t *' {aka '{re:long :re?}int *'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4'
     |
    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/memset-1.c b/gcc/testsuite/gcc.dg/analyzer/memset-1.c
index 94c5a1b7c92..75aef53d348 100644
--- a/gcc/testsuite/gcc.dg/analyzer/memset-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/memset-1.c
@@ -6,8 +6,9 @@ 
 void test_1 (void)
 {
   char buf[256];
-  memset (buf, 0, 256);
+  void *p = memset (buf, 0, 256);
   __analyzer_eval (buf[42] == 0); /* { dg-warning "TRUE" } */
+  __analyzer_eval (p == buf); /* { dg-warning "TRUE" } */
 }
 
 /* As above, but with __builtin_memset.  */
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
index 57bccf4f2eb..02dba7a3234 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
@@ -86,8 +86,11 @@  class copy_across_boundary_fn : public known_function
 
     if (tree cst = num_bytes_sval->maybe_get_constant ())
       if (zerop (cst))
-	/* No-op.  */
-	return;
+	{
+	  /* No-op.  */
+	  model->update_for_zero_return (cd, true);
+	  return;
+	}
 
     const region *sized_src_reg = mgr->get_sized_region (src_reg,
 							 NULL_TREE,
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
index de887dbad83..806cb90ef56 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
@@ -147,8 +147,11 @@  public:
 
     if (tree cst = num_bytes_sval->maybe_get_constant ())
       if (zerop (cst))
-	/* No-op.  */
-	return;
+	{
+	  /* No-op.  */
+	  cd.set_any_lhs_with_defaults ();
+	  return;
+	}
 
     const region *sized_src_reg = mgr->get_sized_region (src_reg,
 							 NULL_TREE,