Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda object + c callback sigsegv

Tags:

c++

c

c++11

lambda

If I implement a C callback like this:

register_callback([](/*some args*/){/*some stuff*/});

I get a SIGSEGV when it triggers, but if I register it like this:

auto const f([](/*some args*/){/*some stuff*/});

register_callback(f);

Then it works fine. Of particular interest (to me) is the stack trace produced by the address sanitizer:

ASAN:SIGSEGV
=================================================================
==22904==ERROR: AddressSanitizer: SEGV on unknown address 0x7f1582c54701 (pc 0x7f1582c54701 sp 0x7f1582c544a8 bp 0x7f1582c54510 T2)
    #0 0x7f1582c54700 ([stack:22906]+0x7fc700)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ??:0 ??

It looks, as if the function pointer pointed to the stack. Does pushing a lambda onto the stack push code onto the stack? Since I capture nothing the location of the function pointer is a mystery to me. What is happening? No optimization flags were used. I am not looking for workarounds.

EDIT: Apparently the '+' was the key to a working example. I don't know why it is necessary. Remove the '+' and the example with compile but SIGSEGV will be triggered with both clang-3.5 and gcc-4.9.

#include <curl/curl.h>

#include <ostream>

#include <iostream>

int main()
{
  auto const curl(curl_easy_init());

  if (curl)
  {
    curl_easy_setopt(curl, CURLOPT_URL, "cnn.com");

    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

/*
    auto const f([](char* const ptr, size_t const size, size_t const nmemb,
      void* const data)
      {
        *static_cast<::std::ostream*>(data) << ptr;

        return size * nmemb;
      }
    );

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +f);
*/

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
      +[](char* const ptr, size_t const size, size_t const nmemb,
        void* const data)
        {
          *static_cast<::std::ostream*>(data) << ptr;

          return size * nmemb;
        }
    );

    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &::std::cout);

    curl_easy_perform(curl);

    curl_easy_cleanup(curl);
  }

  return 0;
}
like image 681
user1095108 Avatar asked Jan 19 '15 13:01

user1095108


1 Answers

curl_easy_setopt is defined as (in curl/easy.h):

CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);

That means that the third argument param must be of a type that can be passed as a C variadic. Unfortunately, while curl_easy_setopt is expecting a function pointer, passing class objects (and lambdas are class objects) is "conditionally-supported with implementation-defined semantics" ([expr.call]/7), so the compiler accepts it but then curl_easy_setopt tries to interpret the lambda object as a function pointer, with catastrophic results.

The object that you actually pass is a captureless lambda which means that it is an empty class object, of size 1 byte (all most-derived objects must be at least one byte in size). The compiler will promote that argument to a word-size integer (4 bytes on 32-bit, 8 bytes on 64-bit) and either pass 0 or leave that register/stack slot unset, meaning that garbage gets passed (since the lambda doesn't actually use its memory footprint when called).

like image 162
ecatmur Avatar answered Sep 20 '22 03:09

ecatmur