13

I've spent quite a few hours today trying to understand why this code segfaults on g++6.2 and g++7.0, while happily working as intended on clang++3.9 (and 4.0).

I reduced the issue to a 85 lines self-contained code snippet, which does not segfault upon normal execution, but always reports an error under UBSAN.

The issue is reproducible on wandbox, by compiling with g++7, enabling optimizations and passing -fsanitize=undefined as an extra flag.

This is what UBSAN reports:

prog.cc: In function 'int main()':
prog.cc:61:49: warning: 'ns#0' is used uninitialized in this function [-Wuninitialized]
         ([&] { ([&] { n.execute(ns...); })(); })();
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
prog.cc:28:10: note: 'ns#0' was declared here
     auto execute(TNode& n, TNodes&... ns)
          ^~~~~~~
prog.cc:30:9: runtime error: member call on null pointer of type 'struct node_then'

g++ claims that ns#0 is uninitialized inside the "lambda gibberish" (which simulates the for_tuple from the original snippet). Now, some very interesting things occur:

  • If I remove the "lambda gibberish", transforming line 61 into

     n.execute(ns...);
    

    then UBSAN stops complaining.

  • If I change the capture list from [&] to [&n, &ns...], UBSAN stops complaining as well:

     ([&](auto) { ([&n, &ns...] { n.execute(ns...); })(); })(0);
    

    ...wait what? How is that different from [&]?

Applying the above discoveries to the original code snippet fixes the segfaults.

Is this a g++ bug? Or is there any undefined behavior in my code?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    _105 lines self-contained code snippet_ isn't exactly a minimal example. Anyway, inline brackets transform it in a minimal, working one. :-) – skypjack Nov 15 '16 at 20:40
  • 2
    I see a lot of really dangerous temporaries, copies, and blind reference storing in your code. If you make one tiny mistake, you get UB. I'd be shocked if it didn't have a tiny mistake somewhere; finding it is hard, and that is why you shouldn't write code like that. And once you have UB, utterly innocuous changes causing things to break or unbreak is par for the course. – Yakk - Adam Nevraumont Nov 15 '16 at 21:26
  • Your 105 lines, with a chain of start and wait and thens -- you are saying having one fewer `then` causes it to not generate your issue? If not, how is it minimal? – Yakk - Adam Nevraumont Nov 15 '16 at 21:29
  • Sorry if the "minimal" example is 105 lines long. I am trying to minimize it further but I'm finding it quite difficult. @Yakk: yes, as absurd as it may sound, removing one of the `.when_all().then()` calls from the chain stops generating the issue. – Vittorio Romeo Nov 15 '16 at 21:54
  • @Yakk and skypjack: I've managed to reduce the example to 85 lines and to [reproduce the UBSAN report on wandbox](http://melpon.org/wandbox/permlink/ACW1w5CDsyK6rSB6). I am writing particularly *"dangerous"* code to experiment with no-allocation asynchronous chains. I would like to pinpoint the tiny mistake that introduces UB *(or report a g++ bug)* so that I do not repeat it in the future and to understand what's going wrong here. – Vittorio Romeo Nov 15 '16 at 22:08
  • 2
    Regardless, the thing that puzzles me the most is that the issue is not reproduced when `[&]` is changed into `[&n, &ns...]`. This smells of a compiler bug to me, as I would expect the two capture lists to be equivalent. – Vittorio Romeo Nov 15 '16 at 22:12
  • @VittorioRomeo I'm not that skilled to reassure you, but I looked over the working draft and found no evidence of differences. Actually I would expect them to be equivalent. Maybe Yakk can give us more details. Anyway, it can still trigger differences in an UB somewhere, so it's hard to say if it's a bug of GCC or not from such an example. – skypjack Nov 15 '16 at 22:45
  • It also works with single level lambda. `[&](){n.execute(ns...);}();` – Arunmu Nov 16 '16 at 08:34
  • @Arunmu: [cannot reproduce](http://melpon.org/wandbox/permlink/N39qWIIoPLjOabsk). – Vittorio Romeo Nov 16 '16 at 09:08
  • @VittorioRomeo I said it "works" :) – Arunmu Nov 16 '16 at 10:25
  • 1
    @Arunmu: whoops - you're right. I found [bug #71386](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71386) which looks related to the issue here. – Vittorio Romeo Nov 16 '16 at 12:32
  • I used your code with GCC 6.1.0 on Wandbox and it's working perfectly. I think it's a bug introduced in GCC 7. – informaticienzero Nov 18 '16 at 09:09

1 Answers1

5

This has nothing to do with temporaries: it's a gcc7.0 optimizer bug. This is a simpler reproducer:

#include <utility>

struct root
{
  template <typename TNode, typename... TNodes>
  void start(TNode n, TNodes... ns)
  {
    n->execute(ns...);
  }
};

template <typename TParent>
struct node_then
{
  TParent *_p;

  node_then(TParent *p) : _p{ p }
  {
  }

  auto execute()
  {
  }

  template <typename TNode, typename... TNodes>
  auto execute(TNode n, TNodes... ns)
  {
    n->execute(ns...);
  }

  template <typename... TNodes>
  auto start(TNodes... ns)
  {
    _p->start(this, ns...);
  }
};

template <typename TParent>
struct node_wait_all
{
  TParent *_p;

  node_wait_all(TParent *p) : _p{ p }
  {
  }

  template <typename TNode, typename... TNodes>
  auto execute(TNode n, TNodes... ns)
  {
    ([&] { ([&] { n->execute(ns...); })(); })();
  }

  template <typename... TNodes>
  auto start(TNodes... ns)
  {
    _p->start(this, ns...);
  }
};


int main()
{
  //node_wait_all<root> obj(new root());
  //node_then<node_wait_all<root>> obj2(new node_wait_all<root>(new root()));
  node_then<node_then<node_wait_all<root>>> obj3(new node_then<node_wait_all<root>>(new node_wait_all<root>(new root())));
  obj3.start();
}

Output:

prog.cc: In function 'int main()':
prog.cc:67:1: internal compiler error: in visit_ref_for_mod_analysis, at ipa-prop.c:2308
 }
 ^
0x96c4d6 visit_ref_for_mod_analysis
    /home/heads/gcc/gcc-source/gcc/ipa-prop.c:2308
0x8f615d walk_stmt_load_store_addr_ops(gimple*, void*, bool (*)(gimple*, tree_node*, tree_node*, void*), bool (*)(gimple*, tree_node*, tree_node*, void*), bool (*)(gimple*, tree_node*, tree_node*, void*))
    /home/heads/gcc/gcc-source/gcc/gimple-walk.c:817
0x9761a2 ipa_analyze_params_uses_in_bb
    /home/heads/gcc/gcc-source/gcc/ipa-prop.c:2335
0x9761a2 analysis_dom_walker::before_dom_children(basic_block_def*)
    /home/heads/gcc/gcc-source/gcc/ipa-prop.c:2415
0x10c8472 dom_walker::walk(basic_block_def*)
    /home/heads/gcc/gcc-source/gcc/domwalk.c:265
0x977ceb ipa_analyze_node(cgraph_node*)
    /home/heads/gcc/gcc-source/gcc/ipa-prop.c:2486
0x1108f0a ipcp_generate_summary
    /home/heads/gcc/gcc-source/gcc/ipa-cp.c:5036
0xa4759c execute_ipa_summary_passes(ipa_opt_pass_d*)
    /home/heads/gcc/gcc-source/gcc/passes.c:2167
0x7d6b45 ipa_passes
    /home/heads/gcc/gcc-source/gcc/cgraphunit.c:2311
0x7d6b45 symbol_table::compile()
    /home/heads/gcc/gcc-source/gcc/cgraphunit.c:2425
0x7d8616 symbol_table::compile()
    /home/heads/gcc/gcc-source/gcc/cgraphunit.c:2587
0x7d8616 symbol_table::finalize_compilation_unit()
    /home/heads/gcc/gcc-source/gcc/cgraphunit.c:2584
Please submit a full bug report,
with preprocessed source if appropriate.
Please include the complete backtrace with any bug report.
See <http://gcc.gnu.org/bugs.html> for instructions.

Link: http://melpon.org/wandbox/permlink/E11fOumFJda6OW6m

To aid in this code's comprehension I'm using a powerful debugging tool: paint.exe

enter image description here

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • Tested with Clang, working perfectly. On GCC 6.1.0 without optimization, got an error message that suggests to report bug. With optimization, it works. Strange, but yes, that's a bug. – informaticienzero Nov 18 '16 at 11:09