WindowsAppSDK icon indicating copy to clipboard operation
WindowsAppSDK copied to clipboard

Windows.Data.Pdf.PdfDocument's destructor re-enters the message loop (C++/WinRT)

Open fredemmott opened this issue 2 years ago • 3 comments

Describe the bug

This makes:

struct Foo : public Bar {
  PdfDocument pdf;
};

... unsafe, if Foo is destructed from the main event loop;

this leads to:

  1. Entering message loop
  2. deleting Foo for some application-specific reason
  3. Entering ~Foo
  4. Entering ~PdfDocument
  5. ... re-entering the message loop while we're still in ~Foo, so:
  • the pointer (raw/unique/shared/whatever) hasn't yet been cleared because deletion is in progress
  • the object now has an invalid vtable
  • depending on the application logic, this may lead to a double-free

Steps to reproduce the bug

  • create struct like above in event loop, with make_unique
  • delete in event loop if set
  • double-free

Expected behavior

destructors should not enter message loop

Screenshots

No response

NuGet package version

Windows App SDK 1.2.4: 1.2.230217.4

Packaging type

Unpackaged

Windows version

Windows 10 version 22H2 (19045, 2022 Update)

IDE

Other

Additional context

Workaround:

PDFFilePageSource::~PDFFilePageSource() {
  if (mPDFDocument) {
    // Windows.Data.Pdf.PdfDocument's destructor re-enters the message loop -
    // which means we can do:
    // 1. Enter message loop
    // 2. Enter ~PDFFilePageSource()
    // 3. Enter ~PdfDocument()
    // 4. Re-enter the message loop while `this` is partially destructed, and
    //    has an invalid vtable
    // 5. make pure virtual calls, double-free, or other badness

    // Work around this by rescheduling the deletion until 'later'
    [](auto deleteLater) -> winrt::fire_and_forget {
      // wil::resume_foreground guarantees that we will be rescheduled, even
      // though we're not changing threads.
      co_await wil::resume_foreground(winrt::Microsoft::UI::Dispatching::
                                        DispatcherQueue::GetForCurrentThread());
    }(std::move(mPDFDocument));
  }
}

fredemmott avatar Mar 15 '23 19:03 fredemmott

Thanks for the report & workaround, we will add this to our Win32 documentation.

gabbybilka avatar Mar 16 '23 16:03 gabbybilka

Shouldn't this issue be fixed, instead of a workaround being glorified in docs?

sylveon avatar Apr 05 '23 21:04 sylveon

This bug makes anything using the PDF libraries defy standard expectations: it makes it so that the destructor can call any other code in your program - i.e. it makes the destructor have all the potential side effects of a co_await. This is not just a documentation issue, it completely breaks the C++ programming model.

fredemmott avatar Jul 11 '24 21:07 fredemmott