llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.16k stars 12.03k forks source link

Clang miscompilation producing double destroy coro local variable #109542

Open kelbon opened 1 month ago

kelbon commented 1 month ago

https://godbolt.org/z/KqqK1aqv6


struct inplace_generator_promise {
  using handle_type = std::coroutine_handle<inplace_generator_promise>;

  handle_type get_return_object() noexcept {
    return handle_type::from_promise(*this);
  }

  std::suspend_always yield_value(int) noexcept {
    return {};
  }

  static constexpr std::suspend_never initial_suspend() noexcept {
    return {};
  }

  std::suspend_always final_suspend() const noexcept {
    return {};
  }
  static constexpr void return_void() noexcept {
  }
  [[noreturn]] static void unhandled_exception() {
    throw;
  }
};

struct inplace_generator {
  using promise_type = inplace_generator_promise;
  using handle_type = std::coroutine_handle<promise_type>;

  handle_type handle;

  inplace_generator(std::coroutine_handle<promise_type> handle) noexcept : handle(handle) {
  }

  ~inplace_generator() {
    handle.destroy();
  }
};

struct dctor {
  dctor() {
    std::cout << "CREATED\n";
  }
  dctor(dctor&&) = delete;
  void operator=(dctor&&) = delete;
  ~dctor() {
    std::cout << "DELETED\n";
  }
};
inplace_generator starter(){
// double destroyed, firstly after throw, second in handle.destroy(), must not be destroyed in handle.destroy after it
    dctor d; 
    co_yield {};
    throw 0;
}

[[gnu::noinline]] void bug_reproducer() {
    starter().handle.resume();
}
llvmbot commented 1 month ago

@llvm/issue-subscribers-coroutines

Author: None (kelbon)

https://godbolt.org/z/KqqK1aqv6 ```cpp struct inplace_generator_promise { using handle_type = std::coroutine_handle<inplace_generator_promise>; handle_type get_return_object() noexcept { return handle_type::from_promise(*this); } std::suspend_always yield_value(int) noexcept { return {}; } static constexpr std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() const noexcept { return {}; } static constexpr void return_void() noexcept { } [[noreturn]] static void unhandled_exception() { throw; } }; struct inplace_generator { using promise_type = inplace_generator_promise; using handle_type = std::coroutine_handle<promise_type>; handle_type handle; inplace_generator(std::coroutine_handle<promise_type> handle) noexcept : handle(handle) { } ~inplace_generator() { handle.destroy(); } }; struct dctor { dctor() { std::cout << "CREATED\n"; } dctor(dctor&&) = delete; void operator=(dctor&&) = delete; ~dctor() { std::cout << "DELETED\n"; } }; inplace_generator starter(){ // double destroyed, firstly after throw, second in handle.destroy(), must not be destroyed in handle.destroy after it dctor d; co_yield {}; throw 0; } [[gnu::noinline]] void bug_reproducer() { starter().handle.resume(); } ```
kelbon commented 1 month ago

I also noticed, that removing [[noreturn]] in minimized version fixes double free

rnk commented 1 month ago

(Sorry for the noise, I see this is EH-related, which is out of scope for us)

dtcxzyw commented 1 month ago

Seems related to https://github.com/llvm/llvm-project/issues/61900. cc @ChuanqiXu9

ChuanqiXu9 commented 1 month ago

Given the current one is not related to optimization, it may not be strictly the same issue. But they may share the solution.

kelbon commented 1 month ago

Im remembering bug in clang, something like:

return a;
label:
foo();

And code was incorrectly deleted as unreachable, now i see, that [[noreturn]] in debug version removes 35% of generateed code. May be just again "unreachable" code deleted, while its reachable?