Сопрограммы C ++: допустимо ли вызывать handle.destroy из последней точки приостановки?

Допустимо ли вызывать handle.destroy() из-за окончательной приостановки сопрограммы C ++?

Насколько я понимаю, это должно быть нормально, потому что сопрограмма в настоящее время приостановлена ​​и больше не будет возобновлена.

Тем не менее, AddressSanitizer сообщает heap-use-after-free для следующего фрагмента кода:

#include <experimental/coroutine>
#include <iostream>

using namespace std;

struct final_awaitable {
   bool await_ready() noexcept { return false; }
   void await_resume() noexcept {}
   template<typename PROMISE> std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<PROMISE> coro) noexcept {
      coro.destroy(); // Is this valid?
      return std::experimental::noop_coroutine();
   }
};

struct task {
   struct promise_type;
   using coro_handle = std::experimental::coroutine_handle<promise_type>;

   struct promise_type {
      task get_return_object() { return {}; }
      auto initial_suspend() { return std::experimental::suspend_never(); }
      auto final_suspend() noexcept { return final_awaitable(); }
      void unhandled_exception() { std::terminate(); }
      void return_void() {}
   };
};

task foo() {
    cerr << "foo\n";
    co_return;
}

int main() {
   auto x = foo();
}

при компиляции с clang 11.0.1 и флагами компиляции -stdlib=libc++ --std=c++17 -fcoroutines-ts -fno-exceptions -fsanitize=address. (см. https://godbolt.org/z/eq6eoc)

(упрощенная версия моего реального кода. Вы можете найти полный код в https://godbolt.org/z/8Yadv1)

Это проблема в моем коде или неправильный результат в AddressSanitizer?

См. также:  Очистить область виджета ячейки в блокноте Jupyter из блокнота
Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 1
  1. Vogelsgesang

    Это совершенно верно, если вы на 100% уверены, что впоследствии никто не будет использовать обещание сопрограммы. вызов coroutine_handle::destroy эквивалентен вызову деструктора обещаний сопрограммы.

    Если это так, то зачем делать это для начала? просто верните std::suspend_never из final_suspend

    std::suspend_never final_suspend() const noexcept { return {}; }
    

    Это эквивалент вашего кода. мы хотим приостановить сопрограмму в final_suspend, если мы хотим сделать что-то значимое с обещанием сопрограммы после завершения сопрограммы, например, вернуть сохраненный результат сопрограммы. поскольку ваш объект task ничего не хранит и не возвращает, я не понимаю, зачем его окончательно приостанавливать.

    Обратите внимание: если вы используете сторонние библиотеки, такие как мой concurrencpp, вам необходимо убедиться, что Это нормально — разрушать чужие обещания. Обещание сопрограммы может быть приостановлено, но по-прежнему на него ссылается coroutine_handle где-то еще. Это восходит к пункту №1. В случае с моей библиотекой это небезопасно, потому что может быть, что объект result все еще ссылается на нее.

    В заключение, можно позвонить coroutine_promise::destroy, если:

    1. сопрограмма приостановлена ​​(когда вы достигнете final_suspend, это будет)
    2. никто не будет использовать это обещание сопрограммы после уничтожения (убедитесь, что нет будущего объекта, который ссылается на эту сопрограмму!)
    3. destroy ранее не вызывался (двойное удаление)

    Спасибо за Ваш ответ. Так вы согласны с тем, что это ложное срабатывание в address-sanitizer? person Vogelsgesang; 01.02.2021

    На ваш вопрос: Если это так, то зачем делать это для начала? просто верните std :: suspend_ Never из final_suspend: приведенный выше фрагмент является только упрощенной версией. Вы можете найти более полный пример в godbolt.org/z/8Yadv1 person Vogelsgesang; 01.02.2021

    Это очень странно. Согласен. Я не понимаю, чего хочет TSAN. Как ни странно, небольшое изменение кода решает проблему: godbolt.org/z/sjW6zs. К настоящему времени у меня есть большой опыт использования сопрограмм, и я могу сказать вам, что ВСЕ компиляторы в настоящее время имеют ГЛАВНЫЕ проблемы с правильной компиляцией сопрограмм. Думаю, это нормально, учитывая, что эта функция по-прежнему считается экспериментальной. person Vogelsgesang; 01.02.2021

    К сожалению, мне нужно await_suspend, чтобы вернуть дескриптор сопрограммы для получения симметричной передачи. В настоящее время я использую аналогичный обходной путь для вашего редактирования (см. godbolt.org/z/zW3jTG) person Vogelsgesang; 01.02.2021

    Я предполагаю, что происходит то, что clang сохраняет дескриптор сопрограммы, возвращаемый await_suspend, внутри самого кадра сопрограммы … В этом случае ASAN будет правильным сообщать об ошибке, учитывая, что кадр сопрограммы был уничтожен. clang должен использовать стек вместо кадра сопрограммы для этого значения. Похоже на баг во внешнем интерфейсе … person Vogelsgesang; 01.02.2021

    Такова жизнь. Я помню, что до Visual Studio 2017 использование std::tuple просто приводило к сбою компилятора. Вы не могли использовать функцию с 2011 по 2016 ~ 17. глядя на это, я могу только улыбнуться по сравнению с другими зверствами, совершенными компиляторами C ++, которые я видел. потерпи. person Vogelsgesang; 01.02.2021

    оказывается, это уже исправлено в clang как часть reviews.llvm.org/D90990. Считая дни до следующего релиза clang … person Vogelsgesang; 08.02.2021

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: