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:
parent
828770c60c
commit
ba2ebc24a1
@ -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 });
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user