Bug 117508 - [13/14/15/16 Regression] Weird debug location info for C++ std::initializer_list
Summary: [13/14/15/16 Regression] Weird debug location info for C++ std::initializer_list
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: debug (show other bugs)
Version: 15.0
: P2 normal
Target Milestone: 13.5
Assignee: Not yet assigned to anyone
URL:
Keywords: needs-bisection, wrong-debug
Depends on:
Blocks:
 
Reported: 2024-11-08 17:45 UTC by Jonathan Wakely
Modified: 2025-07-11 09:21 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work: 11.5.0
Known to fail: 12.1.0
Last reconfirmed: 2025-01-22 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jonathan Wakely 2024-11-08 17:45:22 UTC
#include <string>

struct X {
  X(std::initializer_list<std::string>) { }
};

void test01() {
  X x { "E", "E", "T", "T" };
  return;
}

int main() {
  test01();
  return 0;
}

With GCC 11 debugging this works as expected, but since GCC 12.1 stepping into test01 jumps around while constructing the initializer_list:

$ gdb -q  -ex start -ex step -ex n -ex n -ex n -ex cont -ex q ./a.out
Reading symbols from ./a.out...
Temporary breakpoint 1 at 0x4023b2: file 83709.cc, line 13.
Starting program: /tmp/a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Temporary breakpoint 1, main () at 83709.cc:13
13        test01();
test01 () at 83709.cc:10
10      }
8         X x { "E", "E", "T", "T" };
10      }
9         return;
Continuing.
[Inferior 1 (process 830655) exited normally]

We start on line 13 but after stepping into test01 we're on line 10 (the closing brace of test01), then "next" takes us to line 8 where the initializer_list is used, then "next" takes us back to line 10, then to line 9.

With GCC 11 it's great:

Temporary breakpoint 1, main () at 83709.cc:13
13        test01();
test01 () at 83709.cc:8
8         X x { "E", "E", "T", "T" };
9         return;
10      }
main () at 83709.cc:14
14        return 0;
Continuing.
[Inferior 1 (process 830849) exited normally]
Comment 1 Jonathan Wakely 2024-11-08 17:46:33 UTC
I think this usually happens when some subexpression doesn't have accurate location info, so it's just "somewhere in test01" which gdb treats as the closing brace.
Comment 2 Andrew Pinski 2024-11-08 18:12:19 UTC
I thought I saw this before ...
Comment 3 Andrew Pinski 2024-11-08 18:22:01 UTC
for me this is what I get:
In GCC 13:
  <bb 2> :
  [/app/example.cpp:10:1] _15 = &D.35543;
...

But in GCC 14 and the trunk:
  <bb 2> :
  [/app/example.cpp:8:28] _15 = &D.36094;


So for me it looks like it was fixed in GCC 14
Comment 4 Jonathan Wakely 2024-11-08 19:01:48 UTC
Hmm, you're right, for the reduced case it is fixed in GCC 14.1

But for the original case I reduced, it's not:

#include <unordered_map>
#include <string>

void test01() {
  std::unordered_map<std::string, std::string> m
  { {"E", "E" }, { "T", "T" } };

  m.insert({ {"E", "E" }, { "T", "T" } });
}

int main() {
  test01();
  return 0;
}

Temporary breakpoint 1, main () at 83709.cc:12
12        test01();
test01 () at 83709.cc:9
9       }
6         { {"E", "E" }, { "T", "T" } };
9       }
8         m.insert({ {"E", "E" }, { "T", "T" } });
Continuing.

We step into line 9, then line 6, then line 9, then line 8. That's exactly the same behaviour as for the reduced one, but still present on trunk.
Comment 5 Jonathan Wakely 2024-11-08 19:04:12 UTC
(sorry for not double-checking the reduced version on trunk before reporting it)
Comment 6 Jonathan Wakely 2024-11-08 19:21:20 UTC
namespace std
{
  /// initializer_list
  template<class _E>
    class initializer_list
    {
    public:
      using size_type = decltype(sizeof(1));

    private:
      const _E*			_M_array;
      size_type			_M_len;

      // The compiler can call a private constructor.
      constexpr initializer_list(const _E* __a, size_type __l)
      : _M_array(__a), _M_len(__l) { }

    public:
      constexpr initializer_list() noexcept
      : _M_array(0), _M_len(0) { }

      // Number of elements.
      constexpr size_type
      size() const noexcept { return _M_len; }

      // First element.
      constexpr const _E*
      begin() const noexcept { return _M_array; }

      // One past the last element.
      constexpr const _E*
      end() const noexcept { return begin() + size(); }
    };
}

struct V {
  V(const char*) { }
  ~V() { }
};

struct X {
  X(std::initializer_list<V>) { }
};

void test01() {
  X x { {"E"}, {"E"}, {"T"}, {"T"} };
  return;
}

int main() {
  test01();
  return 0;
}


This only jumps to the closing brace once:

Temporary breakpoint 1, main () at 83709.cc:51
51        test01();
test01 () at 83709.cc:48
48      }
46        X x { {"E"}, {"E"}, {"T"}, {"T"} };
47        return;
48      }
Continuing.
Comment 7 Jonathan Wakely 2024-11-08 19:28:29 UTC
Seems to have started with r12-6329 

c++: EH and partially constructed aggr temp [PR66139]

Now that PR94041 is fixed, I can return to addressing PR66139, missing
cleanups for partially constructed aggregate temporaries.  My previous
approach of calling split_nonconstant_init in cp_gimplify_init_expr broke
because gimplification is too late to be introducing destructor calls.  So
instead I now call it under cp_fold_function, just before cp_genericize;
doing it from cp_genericize itself ran into trouble with the rewriting of
invisible references.

So now the prediction in cp_gimplify_expr that cp_gimplify_init_expr
might need to replace references to TARGET_EXPR_SLOT within
TARGET_EXPR_INITIAL has come to pass.  constexpr.c already had the simple
search-and-replace tree walk I needed, but it needed to be fixed to actually
replace all occurrences instead of just the first one.

Handling of VEC_INIT_EXPR at gimplify time had similar issues that we worked
around with build_vec_init_elt, so I'm moving that to cp_fold_function as
well.  But it seems that build_vec_init_elt is still useful for setting the
VEC_INIT_EXPR_IS_CONSTEXPR flag, so I'm leaving it alone.

This also fixes 52320, because build_aggr_init of each X from build_vec_init
builds an INIT_EXPR rather than call split_nonconstant_init at that point,
and now that INIT_EXPR gets split later.
Comment 8 Jonathan Wakely 2024-11-08 19:29:44 UTC
So it looks like r12-9430 for PR107154 was only a partial fix.
Comment 9 Andrew Pinski 2024-11-08 23:17:14 UTC
Confirmed.

      [/app/example.cpp:6:31] try
        {
          [/app/example.cpp:9:1] D.53100 = &D.52758;
          [/app/example.cpp:9:1] D.53101 = D.53100;
          [/app/example.cpp:9:1] D.53102 = 1;
          try
            {
              [/app/example.cpp:9:1] std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::pair<const char (&)[2], const char (&)[2]> (D.53101, [/app/example.cpp:9:1] "E", [/app/example.cpp:6:11] "E");
              [/app/example.cpp:9:1] D.53101 = D.53101 + 64;
              [/app/example.cpp:9:1] D.53102 = D.53102 + -1;
              [/app/example.cpp:9:1] std::pair<const std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >::pair<const char (&)[2], const char (&)[2]> (D.53101, [/app/example.cpp:9:1] "T", [/app/example.cpp:6:25] "T");
              [/app/example.cpp:9:1] D.53101 = D.53101 + 64;
              [/app/example.cpp:9:1] D.53102 = D.53102 + -1;
              [/app/example.cpp:9:1] retval.0 = D.53100;
              [/app/example.cpp:9:1] D.53102 = 1;
              try
                {
                  [/app/example.cpp:6:31] D.52760._M_array = &D.52758;
                  [/app/example.cpp:6:31] D.52760._M_len = 2;
Comment 10 Richard Biener 2025-07-11 09:21:49 UTC
GCC 12 branch is being closed.