Skip to content

non-exported variable in module interface unit has multiple addresses #130396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Diltsman opened this issue Mar 8, 2025 · 2 comments
Closed

non-exported variable in module interface unit has multiple addresses #130396

Diltsman opened this issue Mar 8, 2025 · 2 comments
Labels
clang:modules C++20 modules and Clang Header Modules question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!

Comments

@Diltsman
Copy link

Diltsman commented Mar 8, 2025

I posted this on Stack Overflow and it was suggested that I post this to Clang somewhere. I don't know if this is the correct place to do that.


I have a variable exception_transformers in coroutine.cpp. If this variable is in an anonymous namespace, then the output shows that register_exception_handler() accesses it at an address 0x18 bytes beyond where every other function accesses it. If exception_transformers is removed from the anonymous namespace, then it is accessed at the same address by all functions.

Is there some interaction between the C++ module and the anonymous namespace that is causing this? Is there some other cause of this problem?


In n4928 (C++23-ish), 9.8.2.2 [namespace.unnamed]

...all occurrences of unique in a translation unit are replaced by the
same identifier, and this identifier differs from all other
identifiers in the translation unit.

To me, this indicates that both anonymous namespaces behave as if they have the same name, so having the declaration and definition of transform_exception() in two different anonymous namespaces in the same file shouldn't be causing an issue.


https://godbolt.org/z/59EWe5G3b

For clang 19:

module;

#include <coroutine>
#include <expected>
#include <functional>
#include <iostream>
#include <ranges>
#include <stdexcept>
#include <system_error>
#include <type_traits>
#include <vector>

export module mycoroutine;
export import :stdexception;

export {
  namespace exco {
  template <typename T> using result_t = std::expected<T, std::error_code>;
  }
}

namespace {
auto transform_exception() -> exco::result_t<void>;
std::vector<std::function<exco::result_t<void>()>> exception_transformers{transform_exception};
}

export {
  namespace exco {
  auto register_exception_handler(std::function<exco::result_t<void>()> func) {
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << '\n';
    exception_transformers.emplace_back(std::move(func));
    std::cout << "register_exception_handler()" << &exception_transformers
              << ' ' << exception_transformers.size() << ' '
              << sizeof(exception_transformers) << '\n';
  }
  inline auto unerr(auto const t) {
    return std::unexpected{make_error_code(t)};
  }

  template <typename T> struct expected_wrapper {
    // Initialize as errno 0 so there are no restrictions on T
    // caused by initializing this
    exco::result_t<T> m_result{exco::unerr(static_cast<std::errc>(0))};
    expected_wrapper<T> *&m_ptr_to_this;
    expected_wrapper(expected_wrapper<T> *&ptr_to_this)
        : m_ptr_to_this{ptr_to_this} {
      m_ptr_to_this = this;
    }
    operator result_t<T>() { return std::move(m_result); };
  };
  } // namespace exco

  namespace std {
  template <typename T, typename... Args>
  struct coroutine_traits<exco::result_t<T>, Args...> {
    class promise_type;
    template <typename T1> struct awaiter_type {
      exco::result_t<T1> &m_result;
      explicit awaiter_type(exco::result_t<T1> &result) noexcept
          : m_result{result} {}
      auto await_ready() { return m_result.has_value(); }
      auto await_suspend(std::coroutine_handle<promise_type> h) {
        // This should only happen when await_ready() returns false,
        // which means that has_value() returned false.
        h.destroy();
      }
      auto await_resume() {
        // This should only happen when await_ready() returns true,
        // which means that has_value() returned true.
        return m_result.value();
      }
    };

    class promise_type {
      exco::expected_wrapper<T> *m_ptr_to_wrapper;

    public:
      auto initial_suspend() noexcept -> std::suspend_never { return {}; }
      auto final_suspend() noexcept -> std::suspend_never { return {}; }
      auto return_value(std::error_code ec) {
        m_ptr_to_wrapper->m_result = std::unexpected{ec};
      }
      auto return_value(auto &&t) { m_ptr_to_wrapper->m_result = std::move(t); }
      auto get_return_object() {
        return exco::expected_wrapper<T>{m_ptr_to_wrapper};
      }
      auto unhandled_exception() {
        for (auto &f : exception_transformers | std::views::reverse) {
          try {
            auto result = f();
            if (!result.has_value()) {
              m_ptr_to_wrapper->m_result = std::unexpected{result.error()};
              return;
            }
          } catch (...) {
          }
        }
        m_ptr_to_wrapper->m_result = exco::unerr(exco::stdexception::unknown);
      }
      template <typename T1> auto await_transform(exco::result_t<T1> value) {
        m_ptr_to_wrapper->m_result = std::move(value);
        return awaiter_type<T1>{m_ptr_to_wrapper->m_result};
      }
    };
  };
  } // namespace std
}

namespace {
auto transform_exception() -> exco::result_t<void> {
  std::cout << "transform_exception()" << &exception_transformers << ' '
            << exception_transformers.size() << '\n';
  try {
    throw;
  } catch (std::exception const &) {
    return exco::unerr(exco::stdexception::exception);
  }
}
}
@EugeneZelenko EugeneZelenko added clang:modules C++20 modules and Clang Header Modules and removed new issue labels Mar 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 8, 2025

@llvm/issue-subscribers-clang-modules

Author: None (Diltsman)

I posted this on Stack Overflow and it was suggested that I post this to Clang somewhere. I don't know if this is the correct place to do that.

I have a variable exception_transformers in coroutine.cpp. If this variable is in an anonymous namespace, then the output shows that register_exception_handler() accesses it at an address 0x18 bytes beyond where every other function accesses it. If exception_transformers is removed from the anonymous namespace, then it is accessed at the same address by all functions.

Is there some interaction between the C++ module and the anonymous namespace that is causing this? Is there some other cause of this problem?


In n4928 (C++23-ish), 9.8.2.2 [namespace.unnamed]

> ...all occurrences of unique in a translation unit are replaced by the
> same identifier, and this identifier differs from all other
> identifiers in the translation unit.

To me, this indicates that both anonymous namespaces behave as if they have the same name, so having the declaration and definition of transform_exception() in two different anonymous namespaces in the same file shouldn't be causing an issue.


https://godbolt.org/z/59EWe5G3b

For clang 19:

module;

#include &lt;coroutine&gt;
#include &lt;expected&gt;
#include &lt;functional&gt;
#include &lt;iostream&gt;
#include &lt;ranges&gt;
#include &lt;stdexcept&gt;
#include &lt;system_error&gt;
#include &lt;type_traits&gt;
#include &lt;vector&gt;

export module mycoroutine;
export import :stdexception;

export {
  namespace exco {
  template &lt;typename T&gt; using result_t = std::expected&lt;T, std::error_code&gt;;
  }
}

namespace {
auto transform_exception() -&gt; exco::result_t&lt;void&gt;;
std::vector&lt;std::function&lt;exco::result_t&lt;void&gt;()&gt;&gt; exception_transformers{transform_exception};
}

export {
  namespace exco {
  auto register_exception_handler(std::function&lt;exco::result_t&lt;void&gt;()&gt; func) {
    std::cout &lt;&lt; "register_exception_handler()" &lt;&lt; &amp;exception_transformers
              &lt;&lt; ' ' &lt;&lt; exception_transformers.size() &lt;&lt; '\n';
    exception_transformers.emplace_back(std::move(func));
    std::cout &lt;&lt; "register_exception_handler()" &lt;&lt; &amp;exception_transformers
              &lt;&lt; ' ' &lt;&lt; exception_transformers.size() &lt;&lt; ' '
              &lt;&lt; sizeof(exception_transformers) &lt;&lt; '\n';
  }
  inline auto unerr(auto const t) {
    return std::unexpected{make_error_code(t)};
  }

  template &lt;typename T&gt; struct expected_wrapper {
    // Initialize as errno 0 so there are no restrictions on T
    // caused by initializing this
    exco::result_t&lt;T&gt; m_result{exco::unerr(static_cast&lt;std::errc&gt;(0))};
    expected_wrapper&lt;T&gt; *&amp;m_ptr_to_this;
    expected_wrapper(expected_wrapper&lt;T&gt; *&amp;ptr_to_this)
        : m_ptr_to_this{ptr_to_this} {
      m_ptr_to_this = this;
    }
    operator result_t&lt;T&gt;() { return std::move(m_result); };
  };
  } // namespace exco

  namespace std {
  template &lt;typename T, typename... Args&gt;
  struct coroutine_traits&lt;exco::result_t&lt;T&gt;, Args...&gt; {
    class promise_type;
    template &lt;typename T1&gt; struct awaiter_type {
      exco::result_t&lt;T1&gt; &amp;m_result;
      explicit awaiter_type(exco::result_t&lt;T1&gt; &amp;result) noexcept
          : m_result{result} {}
      auto await_ready() { return m_result.has_value(); }
      auto await_suspend(std::coroutine_handle&lt;promise_type&gt; h) {
        // This should only happen when await_ready() returns false,
        // which means that has_value() returned false.
        h.destroy();
      }
      auto await_resume() {
        // This should only happen when await_ready() returns true,
        // which means that has_value() returned true.
        return m_result.value();
      }
    };

    class promise_type {
      exco::expected_wrapper&lt;T&gt; *m_ptr_to_wrapper;

    public:
      auto initial_suspend() noexcept -&gt; std::suspend_never { return {}; }
      auto final_suspend() noexcept -&gt; std::suspend_never { return {}; }
      auto return_value(std::error_code ec) {
        m_ptr_to_wrapper-&gt;m_result = std::unexpected{ec};
      }
      auto return_value(auto &amp;&amp;t) { m_ptr_to_wrapper-&gt;m_result = std::move(t); }
      auto get_return_object() {
        return exco::expected_wrapper&lt;T&gt;{m_ptr_to_wrapper};
      }
      auto unhandled_exception() {
        for (auto &amp;f : exception_transformers | std::views::reverse) {
          try {
            auto result = f();
            if (!result.has_value()) {
              m_ptr_to_wrapper-&gt;m_result = std::unexpected{result.error()};
              return;
            }
          } catch (...) {
          }
        }
        m_ptr_to_wrapper-&gt;m_result = exco::unerr(exco::stdexception::unknown);
      }
      template &lt;typename T1&gt; auto await_transform(exco::result_t&lt;T1&gt; value) {
        m_ptr_to_wrapper-&gt;m_result = std::move(value);
        return awaiter_type&lt;T1&gt;{m_ptr_to_wrapper-&gt;m_result};
      }
    };
  };
  } // namespace std
}

namespace {
auto transform_exception() -&gt; exco::result_t&lt;void&gt; {
  std::cout &lt;&lt; "transform_exception()" &lt;&lt; &amp;exception_transformers &lt;&lt; ' '
            &lt;&lt; exception_transformers.size() &lt;&lt; '\n';
  try {
    throw;
  } catch (std::exception const &amp;) {
    return exco::unerr(exco::stdexception::exception);
  }
}
}

@ChuanqiXu9
Copy link
Member

This is due to #112294

In short, the decls in anonymous namespaces are local to the TU, they shouldn't be accessed by other TUs. Clang should forbid it explicitly but we didn't do it. At least this is a false-negative.

@EugeneZelenko EugeneZelenko added the question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead! label Mar 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:modules C++20 modules and Clang Header Modules question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!
Projects
None yet
Development

No branches or pull requests

4 participants