Skip to content

Unsafe Implementation: std::future undefined behavior when the object is not valid (libc++) #125426

Open
@slotosch

Description

@slotosch

During the qualification for Safety +f the C++ STL at Validas we found this safety relevant issue
The C++ 14 Standard states in requirement 30.6.6-3:
The effect of calling any member function other than the destructor, the move-assignment operator, or valid on a future object for which valid() == false is undefined. [ Note: Implementations are encouraged to detect this case and throw an object of type future_error with an error condition of future_errc::no_- state. —end note ]
While this requirement is optional, or recommended (see "encouraged" wording), not fulfilling it may affects the stability and safety badly.

So strictly speaking the "undefined behaviour" might not be a clear bug, but it is a clear safety issue as stated in the C++ standard.
We think it should be easy to fix and gcc implementation does not suffer this bug.

Steps to reproduce:

Create the future object which state is not valid. For example: default-constructed std::future object, or move the shared state from valid future object, or call get on the valid object, etc

Call function such as get, wait, wait_for etc

Expected behavior:

The function throws future_error exception.

Actual behavior:

LLVM based library : QNX, MacOS: Segmentation fault, core dump.

GNU/Linux stdlib: works as expected

Issue analysis:

Implementation does not check if the current status is valid.

Workaround:

Check future::valid() before the call, or ensure the safe state by design.

Note: The following

if (fut.valid()) fut.wait()

may not guarantee the state of fut object in multitasking code if the future object is shared (for example, because of leaking the pointer or reference to another thread). Note that sharing std::future object is not recommended anyway (use shared_future instead).

Minimal reproducer

#include <future>
#include <iostream>
int main() {
  std::future<void> f;
  try {
    f.get();
  } catch (std::future_error& e) {
    std::cout << "Exception handled: " << e.what() << std::endl;
    return 0;
  }
  return 1;
}

or, a bit more advanced workflow:

#include <future>
#include <iostream>
int main() {
  auto f = std::async(std::launch::async, [](){ return 42; });
  f.get(); // first time is OK
  try {
    f.get(); // second time fails
  } catch (std::future_error& e) {
    std::cout << "Exception handled: " << e.what() << std::endl;
    return 0;
  }
  return 1;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.threadingissues related to threading

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions