Перехват и отладка неверного использования ссылки на локальную переменную внутри перемещенной лямбды

Я столкнулся с трудной для отладки ситуацией в одном из моих реальных проектов, где я случайно получил доступ к ссылке на локальную переменную внутри лямбды, которая была перемещена. Доступ осуществлялся из другого потока, но перемещенная лямбда сохранялась до тех пор, пока не закончился второй поток.

Ошибка возникала только при отключенной оптимизации и была вызвана неосторожным рефакторингом.

Я создал минимальный пример (доступен здесь на wandbox), который воспроизводит проблему:

struct state
{
    int x = 100;
};

template <typename TF>
void eat1(TF&& f)
{
    // Call the lambda.
    f();

    // Simulate waiting for the second thread
    // to finish.
    std::this_thread::sleep_for(1000ms);
}

template <typename TF>
void eat0(TF&& f)
{
    // Move the lambda to some other handler.
    eat1(std::forward<TF>(f));
}

void use_state(state& s)
{
    // Will print `100`.
    std::cout << s.x << "\n";

    // Separate thread. Note that `s` is captured by
    // reference.
    std::thread t{[&s]
        {
            // Simulate computation delay.
            std::this_thread::sleep_for(500ms);

            // Will print garbage.
            std::cout << s.x << "\n";
        }};

    t.detach();
}

int main()
{
    eat0([]
        {
            // Local lambda variable that will be accessed
            // after the lambda is moved.
            state s;

            // Function that takes `s` by reference and
            // accesses it in a separate thread after the
            // lambda is moved.
            use_state(s);
        });
}

Удивительно, но ни один из дезинфицирующих средств и предупреждающих флагов не помог здесь.

Я пробовал следующие комбинации компиляторов и дезинфицирующих средств, с

-Wall -Wextra -Wpedantic -g -O0

флаги всегда включены:

  • Компиляторы: g ++ 6.1.1 на Arch Linux x64; clang ++ 3.8.0 в Arch Linux x64; g ++ 5.3.1 на Fedora x64; clang ++ 3.7.0 на Fedora x64.

  • Дезинфицирующие средства: -fsanitize=address; -fsanitize=undefined, -fsanitize=thread,

Ни одна из комбинаций не дала никакой полезной диагностики. Я ожидал, что AddressSanitizer сообщит мне, что я обращаюсь к висячей ссылке, или UndefinedSanitizer для перехвата UB при обращении к нему, или ThreadSanitizer, чтобы сообщить мне, что отдельный поток обращается к неверному расположению памяти.

Есть ли надежный способ диагностики этой проблемы? Должен ли я опубликовать этот пример на каком-либо трекере ошибок sanitizers как запрос / дефект функции?

1 ответ

Решение

Инструмент memcheck от valgrind обнаружил эту проблему при настройках по умолчанию. Тем не менее, этот вид неприятных ошибок имеет шансы избежать Memcheck. Я не уверен, что проблема будет решена в реальной программе.

Тот факт, что первая лямбда была перемещена, не имеет отношения к проблеме (хотя, возможно, это усложнило процесс отладки). Проблема заключается в доступе к локальной переменной в функции, которая завершила свое выполнение (опять же, тот факт, что доступ произошел из другого потока, только усложнил исследование, но никак не способствовал возникновению ошибки). Тот факт, что первая лямбда была сохранена, ни в коем случае не должен вас защищать - локальные переменные относятся к лямбда- вызову, а не к самой лямбде.

Другие вопросы по тегам