The analyzer was issuing false warnings about uninitialized variables
in C++ in places where NRVO was marking DECL_RESULT with
DECL_BY_REFERENCE.

Fixed thusly.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r16-266-ga1922f0252b3b0.

gcc/analyzer/ChangeLog:
        PR analyzer/111536
        * engine.cc (maybe_update_for_edge): Update for new call_stmt
        param to region_model::push_frame.
        * program-state.cc (program_state::push_frame): Likewise.
        * region-model.cc (region_model::update_for_gcall): Likewise.
        (region_model::push_frame): Add "call_stmt" param.
        Handle DECL_RESULT with DECL_BY_REFERENCE set on it by stashing
        the region of the lhs of the call_stmt in the caller frame,
        and writing a reference to it within the "result" in the callee
        frame.
        (region_model::pop_frame): Don't write back to the LHS for
        DECL_BY_REFERENCE results.
        (selftest::test_stack_frames): Update for new call_stmt param to
        region_model::push_frame.
        (selftest::test_get_representative_path_var): Likewise.
        (selftest::test_state_merging): Likewise.
        (selftest::test_alloca): Likewise.
        * region-model.h (region_model::push_frame): Add "call_stmt"
        param.
        * region.cc: Include "tree-ssa.h".
        (region::can_have_initial_svalue_p): Use ssa_defined_default_def_p
        for ssa names, rather than special-casing it for just parameters.
        This should now also cover DECL_RESULT with DECL_BY_REFERENCE and
        hard registers.
        * sm-signal.cc (update_model_for_signal_handler): Update for new
        call_stmt param to region_model::push_frame.
        * state-purge.cc (state_purge_per_decl::process_worklists):
        Likewise.

gcc/testsuite/ChangeLog:
        PR analyzer/111536
        * c-c++-common/analyzer/hard-reg-1.c: New test.
        * g++.dg/analyzer/nrvo-1.C: New test.
        * g++.dg/analyzer/nrvo-2.C: New test.
        * g++.dg/analyzer/nrvo-pr111536-1.C: New test.
        * g++.dg/analyzer/nrvo-pr111536-1b.C: New test.
        * g++.dg/analyzer/nrvo-pr111536-2.C: New test.
        * g++.dg/analyzer/nrvo-pr111536-2b.C: New test.

Signed-off-by: David Malcolm <dmalc...@redhat.com>
---
 gcc/analyzer/engine.cc                        |  2 +-
 gcc/analyzer/program-state.cc                 |  2 +-
 gcc/analyzer/region-model.cc                  | 90 +++++++++++++++----
 gcc/analyzer/region-model.h                   |  4 +-
 gcc/analyzer/region.cc                        | 14 ++-
 gcc/analyzer/sm-signal.cc                     |  2 +-
 gcc/analyzer/state-purge.cc                   |  2 +-
 .../c-c++-common/analyzer/hard-reg-1.c        |  8 ++
 gcc/testsuite/g++.dg/analyzer/nrvo-1.C        | 18 ++++
 gcc/testsuite/g++.dg/analyzer/nrvo-2.C        | 26 ++++++
 .../g++.dg/analyzer/nrvo-pr111536-1.C         | 11 +++
 .../g++.dg/analyzer/nrvo-pr111536-1b.C        | 12 +++
 .../g++.dg/analyzer/nrvo-pr111536-2.C         | 10 +++
 .../g++.dg/analyzer/nrvo-pr111536-2b.C        | 13 +++
 14 files changed, 186 insertions(+), 28 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/hard-reg-1.c
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-1.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1b.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2b.C

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 0917306fe38..c3e4800f70a 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -5343,7 +5343,7 @@ maybe_update_for_edge (logger *logger,
                      == PK_BEFORE_SUPERNODE);
          function *fun = eedge->m_dest->get_function ();
          gcc_assert (fun);
-         m_model.push_frame (*fun, NULL, ctxt);
+         m_model.push_frame (*fun, nullptr, nullptr, ctxt);
          if (logger)
            logger->log ("  pushing frame for %qD", fun->decl);
        }
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index dbca0369b9a..21f78e5b5c3 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -1231,7 +1231,7 @@ void
 program_state::push_frame (const extrinsic_state &ext_state ATTRIBUTE_UNUSED,
                           const function &fun)
 {
-  m_region_model->push_frame (fun, NULL, NULL);
+  m_region_model->push_frame (fun, nullptr, nullptr, nullptr);
 }
 
 /* Get the current function of this state.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index fc586296ce5..1ee882c98ea 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -6270,7 +6270,7 @@ region_model::update_for_gcall (const gcall &call_stmt,
   }
 
   gcc_assert (callee);
-  push_frame (*callee, &arg_svals, ctxt);
+  push_frame (*callee, &call_stmt, &arg_svals, ctxt);
 }
 
 /* Pop the top-most frame_region from the stack, and copy the return
@@ -6744,6 +6744,10 @@ region_model::on_top_level_param (tree param,
 /* Update this region_model to reflect pushing a frame onto the stack
    for a call to FUN.
 
+   If CALL_STMT is non-NULL, this is for the interprocedural case where
+   we already have an execution path into the caller.  It can be NULL for
+   top-level entrypoints into the analysis, or in selftests.
+
    If ARG_SVALS is non-NULL, use it to populate the parameters
    in the new frame.
    Otherwise, the params have their initial_svalues.
@@ -6752,14 +6756,32 @@ region_model::on_top_level_param (tree param,
 
 const region *
 region_model::push_frame (const function &fun,
+                         const gcall *call_stmt,
                          const vec<const svalue *> *arg_svals,
                          region_model_context *ctxt)
 {
-  m_current_frame = m_mgr->get_frame_region (m_current_frame, fun);
+  tree fndecl = fun.decl;
   if (arg_svals)
     {
+      /* If the result of the callee is DECL_BY_REFERENCE, then
+        we'll need to store a reference to the caller's lhs of
+        CALL_STMT within callee's result.
+        If so, determine the region of CALL_STMT's lhs within
+        the caller's frame before updating m_current_frame.  */
+      const region *caller_return_by_reference_reg = nullptr;
+      if (tree result = DECL_RESULT (fndecl))
+       if (DECL_BY_REFERENCE (result))
+         {
+           gcc_assert (call_stmt);
+           tree lhs = gimple_call_lhs (call_stmt);
+           gcc_assert (lhs);
+           caller_return_by_reference_reg = get_lvalue (lhs, ctxt);
+         }
+
+      /* Update m_current_frame.  */
+      m_current_frame = m_mgr->get_frame_region (m_current_frame, fun);
+
       /* Arguments supplied from a caller frame.  */
-      tree fndecl = fun.decl;
       unsigned idx = 0;
       for (tree iter_parm = DECL_ARGUMENTS (fndecl); iter_parm;
           iter_parm = DECL_CHAIN (iter_parm), ++idx)
@@ -6787,13 +6809,39 @@ region_model::push_frame (const function &fun,
                                         va_arg_idx);
          set_value (var_arg_reg, arg_sval, ctxt);
        }
+
+      /* If the result of the callee is DECL_BY_REFERENCE, then above
+        we should have determined the region within the
+        caller's frame that the callee will be writing back to.
+        Use this now to initialize the reference in callee's frame.  */
+      if (tree result = DECL_RESULT (fndecl))
+       if (DECL_BY_REFERENCE (result))
+         {
+           /* Get reference to the caller lhs.  */
+           gcc_assert (caller_return_by_reference_reg);
+           const svalue *ref_sval
+             = m_mgr->get_ptr_svalue (TREE_TYPE (result),
+                                      caller_return_by_reference_reg);
+
+           /* Get region for default val of DECL_RESULT within the
+              callee.  */
+           tree result_default_ssa = get_ssa_default_def (fun, result);
+           gcc_assert (result_default_ssa);
+           const region *callee_result_reg
+             = get_lvalue (result_default_ssa, ctxt);
+
+           /* Set the callee's reference to refer to the caller's lhs.  */
+           set_value (callee_result_reg, ref_sval, ctxt);
+         }
     }
   else
     {
       /* Otherwise we have a top-level call within the analysis.  The params
         have defined but unknown initial values.
         Anything they point to has escaped.  */
-      tree fndecl = fun.decl;
+
+      /* Update m_current_frame.  */
+      m_current_frame = m_mgr->get_frame_region (m_current_frame, fun);
 
       /* Handle "__attribute__((nonnull))".   */
       tree fntype = TREE_TYPE (fndecl);
@@ -6946,7 +6994,11 @@ region_model::pop_frame (tree result_lvalue,
   /* Pop the frame.  */
   m_current_frame = m_current_frame->get_calling_frame ();
 
-  if (result_lvalue && retval)
+  if (result_lvalue
+      && retval
+      /* Don't write back for DECL_BY_REFERENCE; the writes
+        should have happened within the callee already.  */
+      && !DECL_BY_REFERENCE (result))
     {
       gcc_assert (eval_return_svalue);
 
@@ -8925,7 +8977,7 @@ test_stack_frames ()
   /* Push stack frame for "parent_fn".  */
   const region *parent_frame_reg
     = model.push_frame (*DECL_STRUCT_FUNCTION (parent_fndecl),
-                       NULL, &ctxt);
+                       nullptr, nullptr, &ctxt);
   ASSERT_EQ (model.get_current_frame (), parent_frame_reg);
   ASSERT_TRUE (model.region_exists_p (parent_frame_reg));
   const region *a_in_parent_reg = model.get_lvalue (a, &ctxt);
@@ -8940,7 +8992,8 @@ test_stack_frames ()
 
   /* Push stack frame for "child_fn".  */
   const region *child_frame_reg
-    = model.push_frame (*DECL_STRUCT_FUNCTION (child_fndecl), NULL, &ctxt);
+    = model.push_frame (*DECL_STRUCT_FUNCTION (child_fndecl),
+                       nullptr, nullptr, &ctxt);
   ASSERT_EQ (model.get_current_frame (), child_frame_reg);
   ASSERT_TRUE (model.region_exists_p (child_frame_reg));
   const region *x_in_child_reg = model.get_lvalue (x, &ctxt);
@@ -9033,7 +9086,8 @@ test_get_representative_path_var ()
   for (int depth = 0; depth < 5; depth++)
     {
       const region *frame_n_reg
-       = model.push_frame (*DECL_STRUCT_FUNCTION (fndecl), NULL, &ctxt);
+       = model.push_frame (*DECL_STRUCT_FUNCTION (fndecl),
+                           nullptr, nullptr, &ctxt);
       const region *parm_n_reg = model.get_lvalue (path_var (n, depth), &ctxt);
       parm_regs.safe_push (parm_n_reg);
 
@@ -9279,9 +9333,11 @@ test_state_merging ()
     region_model model0 (&mgr);
     region_model model1 (&mgr);
     ASSERT_EQ (model0.get_stack_depth (), 0);
-    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                      nullptr, nullptr, &ctxt);
     ASSERT_EQ (model0.get_stack_depth (), 1);
-    model1.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, &ctxt);
+    model1.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                      nullptr, nullptr, &ctxt);
 
     placeholder_svalue test_sval (mgr.alloc_symbol_id (),
                                  integer_type_node, "test sval");
@@ -9373,7 +9429,8 @@ test_state_merging ()
   /* Pointers: non-NULL and non-NULL: ptr to a local.  */
   {
     region_model model0 (&mgr);
-    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                      nullptr, nullptr, nullptr);
     model0.set_value (model0.get_lvalue (p, NULL),
                      model0.get_rvalue (addr_of_a, NULL), NULL);
 
@@ -9512,12 +9569,14 @@ test_state_merging ()
      frame points to a local in a more recent stack frame.  */
   {
     region_model model0 (&mgr);
-    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                      nullptr, nullptr, nullptr);
     const region *q_in_first_frame = model0.get_lvalue (q, NULL);
 
     /* Push a second frame.  */
     const region *reg_2nd_frame
-      = model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+      = model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                          nullptr, nullptr, nullptr);
 
     /* Have a pointer in the older frame point to a local in the
        more recent frame.  */
@@ -9544,7 +9603,8 @@ test_state_merging ()
   /* Verify that we can merge a model in which a local points to a global.  */
   {
     region_model model0 (&mgr);
-    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl), NULL, NULL);
+    model0.push_frame (*DECL_STRUCT_FUNCTION (test_fndecl),
+                      nullptr, nullptr, nullptr);
     model0.set_value (model0.get_lvalue (q, NULL),
                      model0.get_rvalue (addr_of_y, NULL), NULL);
 
@@ -10076,7 +10136,7 @@ test_alloca ()
   /* Push stack frame.  */
   const region *frame_reg
     = model.push_frame (*DECL_STRUCT_FUNCTION (fndecl),
-                       NULL, &ctxt);
+                       nullptr, nullptr, &ctxt);
   /* "p = alloca (n * 4);".  */
   const svalue *size_sval = model.get_rvalue (n_times_4, &ctxt);
   const region *reg = model.create_region_for_alloca (size_sval, &ctxt);
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index c1eb9468f4b..2c7f73795e2 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -391,7 +391,9 @@ class region_model
   void update_for_return_gcall (const gcall &call_stmt,
                                 region_model_context *ctxt);
 
-  const region *push_frame (const function &fun, const vec<const svalue *> 
*arg_sids,
+  const region *push_frame (const function &fun,
+                           const gcall *call_stmt,
+                           const vec<const svalue *> *arg_sids,
                            region_model_context *ctxt);
   const frame_region *get_current_frame () const { return m_current_frame; }
   const function *get_current_function () const;
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 4e56cd569d3..efbbca03c7f 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "digraph.h"
 #include "sbitmap.h"
 #include "fold-const.h"
+#include "tree-ssa.h"
 
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/supergraph.h"
@@ -546,15 +547,12 @@ region::can_have_initial_svalue_p () const
 
            case SSA_NAME:
              {
+               /* Some SSA names have an implicit default defined value.  */
                tree ssa_name = decl;
-               /* SSA names that are the default defn of a PARM_DECL
-                  have initial_svalues; other SSA names don't.  */
-               if (SSA_NAME_IS_DEFAULT_DEF (ssa_name)
-                   && SSA_NAME_VAR (ssa_name)
-                   && TREE_CODE (SSA_NAME_VAR (ssa_name)) == PARM_DECL)
-                 return true;
-               else
-                 return false;
+               if (SSA_NAME_IS_DEFAULT_DEF (ssa_name))
+                 return ssa_defined_default_def_p (ssa_name);
+               /* Others don't.  */
+               return false;
              }
            }
        }
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index bbd7d70a7bd..83f2808708e 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -196,7 +196,7 @@ update_model_for_signal_handler (region_model *model,
   gcc_assert (model);
   /* Purge all state within MODEL.  */
   *model = region_model (model->get_manager ());
-  model->push_frame (handler_fun, NULL, NULL);
+  model->push_frame (handler_fun, nullptr, nullptr, nullptr);
 }
 
 /* Custom exploded_edge info: entry into a signal-handler.  */
diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc
index d67802e7eea..7a93ceee9a1 100644
--- a/gcc/analyzer/state-purge.cc
+++ b/gcc/analyzer/state-purge.cc
@@ -730,7 +730,7 @@ state_purge_per_decl::process_worklists (const 
state_purge_map &map,
       worklist.safe_push (iter);
 
     region_model model (mgr);
-    model.push_frame (get_function (), NULL, NULL);
+    model.push_frame (get_function (), nullptr, nullptr, nullptr);
 
     /* Process worklist by walking backwards until we reach a stmt
        that fully overwrites the decl.  */
diff --git a/gcc/testsuite/c-c++-common/analyzer/hard-reg-1.c 
b/gcc/testsuite/c-c++-common/analyzer/hard-reg-1.c
new file mode 100644
index 00000000000..d22a5b570a1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/hard-reg-1.c
@@ -0,0 +1,8 @@
+/* { dg-do compile { target x86_64-*-* } } */
+
+void *
+get_from_hard_reg (void)
+{
+  register void *sp asm ("sp");
+  return sp;
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-1.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-1.C
new file mode 100644
index 00000000000..146b80a3a0c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-1.C
@@ -0,0 +1,18 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct s1
+{
+  s1 (int x) : m_x (x) {}
+  int m_x;
+};
+
+s1 make_s1 (int x)
+{
+  return s1 (x);
+}
+
+void test_1 (int x)
+{
+  s1 s = make_s1 (x);
+  __analyzer_eval (s.m_x == x); // { dg-warning "TRUE" }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-2.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-2.C
new file mode 100644
index 00000000000..e0567c31ae2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-2.C
@@ -0,0 +1,26 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+// { dg-do compile { target c++11 } }
+
+struct s1
+{
+  int t;
+  s1() { t = 42; }
+  s1(const s1& other) { t = other.t; }
+};
+
+s1 inner()
+{
+  return s1{}; // { dg-bogus "uninitialized" }
+}
+
+s1 middle()
+{
+  return inner();
+}
+
+void outer()
+{
+  s1 obj = middle();
+  __analyzer_eval (obj.t == 42); // { dg-warning "TRUE" }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1.C
new file mode 100644
index 00000000000..de447ae7f47
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1.C
@@ -0,0 +1,11 @@
+struct Guard {
+  int i;
+  ~Guard() {}
+};
+Guard lock() {
+  return Guard(); // { dg-bogus "uninitialized" }
+}
+void bar() {
+  Guard foo = lock();
+}
+
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1b.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1b.C
new file mode 100644
index 00000000000..681795da995
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-1b.C
@@ -0,0 +1,12 @@
+// { dg-do compile { target c++11 } }
+
+struct Guard {
+  int i;
+  ~Guard() {}
+};
+Guard lock() {
+  return Guard(); // { dg-bogus "uninitialized" }
+}
+void bar() {
+  auto foo = lock();
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2.C
new file mode 100644
index 00000000000..aa011e223b3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2.C
@@ -0,0 +1,10 @@
+struct g
+{
+  int t;
+  g();
+};
+
+g foo1()
+{
+  return g(); // { dg-bogus "uninitialized" }
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2b.C 
b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2b.C
new file mode 100644
index 00000000000..31df5a86ccc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/nrvo-pr111536-2b.C
@@ -0,0 +1,13 @@
+// { dg-do compile { target c++11 } }
+
+struct g
+{
+  int t;
+  g();
+  g(const g&);
+};
+
+g foo1()
+{
+  return g{}; // { dg-bogus "uninitialized" }
+}
-- 
2.26.3

Reply via email to