QtFuture: Prevent whenAll()/whenAny() from creating reference cycles

whenAll() and whenAny() create a shared context object which is
referenced by the continuation lambda. The refcount of context is only
correctly managed when it is copied non-const to the lambda's
capture list.

Fixes: QTBUG-116731
Pick-to: 6.6 6.6.0
Change-Id: I8e79e1a0dc867f69bbacf1ed873f353a18f6ad38
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Arno Rehn 2023-09-05 09:25:43 +02:00 committed by Mårten Nordheim
parent 828770c60c
commit ba2ebc24a1
2 changed files with 43 additions and 6 deletions

View File

@ -1105,9 +1105,10 @@ void addCompletionHandlersImpl(const std::shared_ptr<ContextType> &context,
{
auto future = std::get<Index>(t);
using ResultType = typename ContextType::ValueType;
future.then([context](const std::tuple_element_t<Index, std::tuple<Ts...>> &f) {
// Need context=context so that the compiler does not infer the captured variable's type as 'const'
future.then([context=context](const std::tuple_element_t<Index, std::tuple<Ts...>> &f) {
context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, f });
}).onCanceled([context, future]() {
}).onCanceled([context=context, future]() {
context->checkForCompletion(Index, ResultType { std::in_place_index<Index>, future });
});
@ -1135,9 +1136,10 @@ QFuture<OutputSequence> whenAllImpl(InputIt first, InputIt last)
qsizetype idx = 0;
for (auto it = first; it != last; ++it, ++idx) {
it->then([context, idx](const ValueType &f) {
// Need context=context so that the compiler does not infer the captured variable's type as 'const'
it->then([context=context, idx](const ValueType &f) {
context->checkForCompletion(idx, f);
}).onCanceled([context, idx, f = *it] {
}).onCanceled([context=context, idx, f = *it] {
context->checkForCompletion(idx, f);
});
}
@ -1175,9 +1177,10 @@ QFuture<QtFuture::WhenAnyResult<typename Future<ValueType>::type>> whenAnyImpl(I
qsizetype idx = 0;
for (auto it = first; it != last; ++it, ++idx) {
it->then([context, idx](const ValueType &f) {
// Need context=context so that the compiler does not infer the captured variable's type as 'const'
it->then([context=context, idx](const ValueType &f) {
context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f });
}).onCanceled([context, idx, f = *it] {
}).onCanceled([context=context, idx, f = *it] {
context->checkForCompletion(idx, QtFuture::WhenAnyResult { idx, f });
});
}

View File

@ -5011,6 +5011,40 @@ void tst_QFuture::continuationsDontLeak()
QVERIFY(continuationIsRun);
}
QCOMPARE(InstanceCounter::count, 0);
{
// QTBUG-116731: Must pass with ASan enabled
bool continuationIsRun = false;
auto f = QtFuture::makeReadyValueFuture(42);
QtFuture::whenAll(f).then([&](auto) { continuationIsRun = true; });
QVERIFY(continuationIsRun);
}
{
// QTBUG-116731: Must pass with ASan enabled
bool continuationIsRun = false;
auto f = QtFuture::makeReadyValueFuture(42);
QList fs{f};
QtFuture::whenAll(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; });
QVERIFY(continuationIsRun);
}
{
// QTBUG-116731: Must pass with ASan enabled
bool continuationIsRun = false;
auto f = QtFuture::makeReadyValueFuture(42);
QtFuture::whenAny(f).then([&](auto) { continuationIsRun = true; });
QVERIFY(continuationIsRun);
}
{
// QTBUG-116731: Must pass with ASan enabled
bool continuationIsRun = false;
auto f = QtFuture::makeReadyValueFuture(42);
QList fs{f};
QtFuture::whenAny(fs.begin(), fs.end()).then([&](auto) { continuationIsRun = true; });
QVERIFY(continuationIsRun);
}
}
// This test checks that we do not get use-after-free