Допустимо ли вызывать 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?
Это совершенно верно, если вы на 100% уверены, что впоследствии никто не будет использовать обещание сопрограммы. вызов
coroutine_handle::destroy
эквивалентен вызову деструктора обещаний сопрограммы.Если это так, то зачем делать это для начала? просто верните
std::suspend_never
изfinal_suspend
Это эквивалент вашего кода. мы хотим приостановить сопрограмму в
final_suspend
, если мы хотим сделать что-то значимое с обещанием сопрограммы после завершения сопрограммы, например, вернуть сохраненный результат сопрограммы. поскольку ваш объектtask
ничего не хранит и не возвращает, я не понимаю, зачем его окончательно приостанавливать.Обратите внимание: если вы используете сторонние библиотеки, такие как мой concurrencpp, вам необходимо убедиться, что Это нормально — разрушать чужие обещания. Обещание сопрограммы может быть приостановлено, но по-прежнему на него ссылается
coroutine_handle
где-то еще. Это восходит к пункту №1. В случае с моей библиотекой это небезопасно, потому что может быть, что объектresult
все еще ссылается на нее.В заключение, можно позвонить
coroutine_promise::destroy
, если:final_suspend
, это будет)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