Bug 56973 - [DR 696] crash when capturing variables in nested lambdas
Summary: [DR 696] crash when capturing variables in nested lambdas
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.8.0
: P2 normal
Target Milestone: 8.0
Assignee: Jason Merrill
URL:
Keywords: c++-lambda, wrong-code
: 79104 (view as bug list)
Depends on:
Blocks: lambdas
  Show dependency treegraph
 
Reported: 2013-04-15 18:35 UTC by Dirk Moermans
Modified: 2024-07-24 15:17 UTC (History)
5 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 6.3.0
Last reconfirmed: 2017-01-14 00:00:00


Attachments
preprocessed file (77.83 KB, application/octet-stream)
2013-04-21 11:41 UTC, Dirk Moermans
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Dirk Moermans 2013-04-15 18:35:00 UTC
The valid C++11 program below segfaults on my machine:

=====
#include <cassert>
#include <cstring>
#include <iterator>

void test(const char* s)
{
   constexpr /*static*/ struct {const char* s;} ar[]={
      {"e4"},
      {"e5"},
      {"Nf3"},
      {"Nc6"},
      {"Bb5"},
      {"a6"}
   };

   auto it=std::begin(ar);
   auto compare=[&it](const char* s)
      {
         assert(std::strcmp(it->s,s)==0);
      };

   auto test=[&](const char* s)
      {
         it=std::begin(ar);
         compare(s);
      };

   test(s);
}

int main(int argc, const char* argv[])
{
   test(argv[1]);
   test(argv[2]);
   test(argv[3]);
   return 0;
}
=====

I compile with: $ g++ -g -O --std=c++11 bug.cc.
I run as: $ ./a.out e4 e4 e4

My machine is a 64 bit linux intel core duo 2 (T4200).

If I make "ar" static the program passes, so I assume that ar is overwritten prematurely on the stack.

The error only occurs from optimization-levels "-O" onwards.
Comment 1 Dirk Moermans 2013-04-21 11:41:33 UTC
Created attachment 29909 [details]
preprocessed file
Comment 2 Dirk Moermans 2013-04-21 11:43:02 UTC
the output of gcc -v

Using built-in specs.
COLLECT_GCC=g++-4.8.0
COLLECT_LTO_WRAPPER=/home/dirk/local/libexec/gcc/x86_64-unknown-linux-gnu/4.8.0/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: ./configure --prefix=/home/dirk/local --program-suffix=-4.8.0 --enable-languages=c,c++
Thread model: posix
gcc version 4.8.0 (GCC)
Comment 3 Martin Sebor 2017-01-14 18:21:55 UTC
I still see the crash with GCC 6 (the program runs fine when compiled with Clang).

$ /build/gcc-6-branch/gcc/xg++ -B /build/gcc-6-branch/gcc -nostdinc++ -I /build/gcc-6-branch/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu -I /build/gcc-6-branch/x86_64-pc-linux-gnu/libstdc++-v3/include -I /src/gcc/6-branch/libstdc++-v3/libsupc++ -I /src/gcc/6-branch/libstdc++-v3/include/backward -I /src/gcc/6-branch/libstdc++-v3/testsuite/util -L /build/gcc-6-branch/x86_64-pc-linux-gnu/libstdc++-v3/src/.libs -O1 -g t.C && gdb -q -ex 'r e4 e4 e4' -ex bt -ex 'x/i $pc' -ex 'p $rdi' ./a.out
Reading symbols from ./a.out...done.
Starting program: /home/msebor/build/tmp/a.out e4 e4 e4

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7336266 in __strcmp_ssse3 () from /lib64/libc.so.6
#0  0x00007ffff7336266 in __strcmp_ssse3 () from /lib64/libc.so.6
#1  0x0000000000400656 in operator() (s=<optimized out>, 
    __closure=<synthetic pointer>) at t.C:19
#2  operator() (s=<optimized out>, __closure=<synthetic pointer>) at t.C:25
#3  test (s=<optimized out>) at t.C:28
#4  0x0000000000400685 in main (argc=<optimized out>, argv=0x7fffffffdf38)
    at t.C:33
=> 0x7ffff7336266 <__strcmp_ssse3+22>:	movlpd (%rdi),%xmm1
$1 = 1

GCC 7 fails to compile the test case with the errors below:

t.C: In lambda function:
t.C:24:26: error: no matching function for call to ‘begin(const test(const char*)::<unnamed struct> [6])’
          it=std::begin(ar);
                          ^
In file included from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:36:0,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/string:51,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/locale_classes.h:40,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ios_base.h:41,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/ios:42,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/ostream:38,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/iterator:64,
                 from t.C:3:
/src/gcc/svn/libstdc++-v3/libsupc++/initializer_list:89:5: note: candidate: template<class _Tp> constexpr const _Tp* std::begin(std::initializer_list<_Tp>)
     begin(initializer_list<_Tp> __ils) noexcept
     ^~~~~
/src/gcc/svn/libstdc++-v3/libsupc++/initializer_list:89:5: note:   template argument deduction/substitution failed:
t.C:24:26: note:   mismatched types ‘std::initializer_list<_Tp>’ and ‘const test(const char*)::<unnamed struct>*’
          it=std::begin(ar);
                          ^
In file included from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/string:51:0,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/locale_classes.h:40,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/ios_base.h:41,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/ios:42,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/ostream:38,
                 from /build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/iterator:64,
                 from t.C:3:
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:48:5: note: candidate: template<class _Container> decltype (__cont.begin()) std::begin(_Container&)
     begin(_Container& __cont) -> decltype(__cont.begin())
     ^~~~~
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:48:5: note:   template argument deduction/substitution failed:
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h: In substitution of ‘template<class _Container> decltype (__cont.begin()) std::begin(_Container&) [with _Container = const test(const char*)::<unnamed struct> [6]]’:
t.C:24:26:   required from here
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:48:50: error: request for member ‘begin’ in ‘__cont’, which is of non-class type ‘const test(const char*)::<unnamed struct> [6]’
     begin(_Container& __cont) -> decltype(__cont.begin())
                                           ~~~~~~~^~~~~
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:58:5: note: candidate: template<class _Container> decltype (__cont.begin()) std::begin(const _Container&)
     begin(const _Container& __cont) -> decltype(__cont.begin())
     ^~~~~
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:58:5: note:   template argument deduction/substitution failed:
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h: In substitution of ‘template<class _Container> decltype (__cont.begin()) std::begin(const _Container&) [with _Container = test(const char*)::<unnamed struct> [6]]’:
t.C:24:26:   required from here
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:58:56: error: request for member ‘begin’ in ‘__cont’, which is of non-class type ‘const test(const char*)::<unnamed struct> [6]’
     begin(const _Container& __cont) -> decltype(__cont.begin())
                                                 ~~~~~~~^~~~~
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:87:5: note: candidate: _Tp* std::begin(_Tp (&)[_Nm]) [with _Tp = const test(const char*)::<unnamed struct>; long unsigned int _Nm = 6]
     begin(_Tp (&__arr)[_Nm])
     ^~~~~
/build/gcc-svn/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/range_access.h:87:5: note:   no known conversion for argument 1 from ‘const test(const char*)::<unnamed struct> [6]’ to ‘const test(const char*)::<unnamed struct> (&)[6]’
Comment 4 Jason Merrill 2017-02-10 18:43:13 UTC
G++ still implements an early proposal for DR 696, whereby a reference to a constant automatic variable from a lambda is replaced by the value of the variable rather than implying a capture.  We need to properly implement DR 696 such that odr-use implies a capture.
Comment 5 Jason Merrill 2017-09-11 09:37:05 UTC
Nathan mentioned today that we have the inverse problem as well: after we've captured i, we should still be able to use its constant value.  Here's an example of things that ought to work and mostly don't:

int main()
{ 
  const int i = 4;
  [] { constexpr int x = i; };
  [=] { &i; constexpr int x = i; };
  [&] { &i; constexpr int x = i; };
  [i] { &i; constexpr int x = i; };
  [&i] { &i; constexpr int x = i; };
}

"Every id-expression within the compound-statement of a lambda-expression that is an odr-use (6.2) of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [ Note: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. Furthermore, such an id-expression does not cause the implicit capture of the entity. — end note ]"

"A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.20) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.1) is applied to e, or e is a discarded-value expression (Clause 8)."
Comment 6 Jason Merrill 2017-09-28 19:40:18 UTC
Author: jason
Date: Thu Sep 28 19:39:45 2017
New Revision: 253266

URL: https://gcc.gnu.org/viewcvs?rev=253266&root=gcc&view=rev
Log:
	PR c++/56973, DR 696 - capture constant variables only as needed.

	* expr.c (mark_use): Split out from mark_rvalue_use and
	mark_lvalue_use.  Handle lambda capture of constant variables.
	(mark_lvalue_use_nonread): New.
	* semantics.c (process_outer_var_ref): Don't capture a constant
	variable until forced.
	* pt.c (processing_nonlambda_template): New.
	* call.c (build_this): Check it.
	* decl2.c (grok_array_decl): Call mark_rvalue_use and
	mark_lvalue_use_nonread.
	* init.c (constant_value_1): Don't call mark_rvalue_use.
	* typeck.c (build_static_cast): Handle lambda capture.

Added:
    trunk/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const6.C
    trunk/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-const7.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/call.c
    trunk/gcc/cp/cp-tree.h
    trunk/gcc/cp/decl2.c
    trunk/gcc/cp/expr.c
    trunk/gcc/cp/init.c
    trunk/gcc/cp/pt.c
    trunk/gcc/cp/semantics.c
    trunk/gcc/cp/typeck.c
    trunk/gcc/testsuite/g++.dg/cpp0x/constexpr-64462.C
    trunk/gcc/testsuite/g++.dg/cpp1y/lambda-generic-const4.C
Comment 7 Paolo Carlini 2018-03-03 00:39:15 UTC
Jason, can we resolve this? I didn't investigate much but I see [DR 696] in the subject and in your last patch, isn't obvious what is still missing?!?
Comment 8 Jason Merrill 2018-03-03 05:23:18 UTC
Yes, this is  fixed now.
Comment 9 Jason Merrill 2024-07-24 15:17:48 UTC
*** Bug 79104 has been marked as a duplicate of this bug. ***