JSON: When clearing duplicate object entries, also clear containers
Previously, if you had multiple entries with the same name in an object, and some of them were again objects or arrays, parsing the JSON document would leak memory. Also, we use std::stable_sort instead of std::sort now, so that we don't accidentally randomize the order of elements with equal keys. [ChangeLog][QtCore][JSON] A memory leak in the JSON parser when reading objects with duplicate keys was fixed. Pick-to: 5.15 6.2 6.3 Fixes: QTBUG-99799 Change-Id: Ic2065f2e490c2d3506a356745542148ad9c24262 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
6f6c8d0618
commit
1b4a5ecc91
@ -379,10 +379,30 @@ error:
|
|||||||
return QCborValue();
|
return QCborValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to retain the _last_ value for any duplicate keys and we need to deref containers.
|
||||||
|
// Therefore the manual implementation of std::unique().
|
||||||
|
template<typename Iterator, typename Compare, typename Assign>
|
||||||
|
static Iterator customAssigningUniqueLast(Iterator first, Iterator last,
|
||||||
|
Compare compare, Assign assign)
|
||||||
|
{
|
||||||
|
first = std::adjacent_find(first, last, compare);
|
||||||
|
if (first == last)
|
||||||
|
return last;
|
||||||
|
|
||||||
|
Iterator result = first;
|
||||||
|
while (++first != last) {
|
||||||
|
if (!compare(*result, *first))
|
||||||
|
++result;
|
||||||
|
if (result != first)
|
||||||
|
assign(*result, *first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ++result;
|
||||||
|
}
|
||||||
|
|
||||||
static void sortContainer(QCborContainerPrivate *container)
|
static void sortContainer(QCborContainerPrivate *container)
|
||||||
{
|
{
|
||||||
using Forward = QJsonPrivate::KeyIterator;
|
using Forward = QJsonPrivate::KeyIterator;
|
||||||
using Reverse = std::reverse_iterator<Forward>;
|
|
||||||
using Value = Forward::value_type;
|
using Value = Forward::value_type;
|
||||||
|
|
||||||
auto compare = [container](const Value &a, const Value &b)
|
auto compare = [container](const Value &a, const Value &b)
|
||||||
@ -417,17 +437,31 @@ static void sortContainer(QCborContainerPrivate *container)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::sort(Forward(container->elements.begin()), Forward(container->elements.end()),
|
// The elements' containers are owned by the outer container, not by the elements themselves.
|
||||||
[&compare](const Value &a, const Value &b) { return compare(a, b) < 0; });
|
auto move = [](Forward::reference target, Forward::reference source)
|
||||||
|
{
|
||||||
|
QtCbor::Element &targetValue = target.value();
|
||||||
|
|
||||||
// We need to retain the _last_ value for any duplicate keys. Therefore the reverse dance here.
|
// If the target has a container, deref it before overwriting, so that we don't leak.
|
||||||
auto it = std::unique(Reverse(container->elements.end()), Reverse(container->elements.begin()),
|
if (targetValue.flags & QtCbor::Element::IsContainer)
|
||||||
[&compare](const Value &a, const Value &b) {
|
targetValue.container->deref();
|
||||||
return compare(a, b) == 0;
|
|
||||||
}).base().elementsIterator();
|
|
||||||
|
|
||||||
// The erase from beginning is expensive but hopefully rare.
|
// Do not move, so that we can clear the value afterwards.
|
||||||
container->elements.erase(container->elements.begin(), it);
|
target = source;
|
||||||
|
|
||||||
|
// Clear the source value, so that we don't store the same container twice.
|
||||||
|
source.value() = QtCbor::Element();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::stable_sort(
|
||||||
|
Forward(container->elements.begin()), Forward(container->elements.end()),
|
||||||
|
[&compare](const Value &a, const Value &b) { return compare(a, b) < 0; });
|
||||||
|
|
||||||
|
Forward result = customAssigningUniqueLast(
|
||||||
|
Forward(container->elements.begin()), Forward(container->elements.end()),
|
||||||
|
[&compare](const Value &a, const Value &b) { return compare(a, b) == 0; }, move);
|
||||||
|
|
||||||
|
container->elements.erase(result.elementsIterator(), container->elements.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,6 +175,7 @@ private Q_SLOTS:
|
|||||||
void fromToVariantConversions();
|
void fromToVariantConversions();
|
||||||
|
|
||||||
void testIteratorComparison();
|
void testIteratorComparison();
|
||||||
|
void noLeakOnNameClash();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString testDataDir;
|
QString testDataDir;
|
||||||
@ -3649,5 +3650,23 @@ void tst_QtJson::testIteratorComparison()
|
|||||||
QVERIFY(t.end() > t.begin());
|
QVERIFY(t.end() > t.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QtJson::noLeakOnNameClash()
|
||||||
|
{
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson("{\"\":{\"\":0},\"\":0}");
|
||||||
|
QVERIFY(!doc.isNull());
|
||||||
|
const QJsonObject obj = doc.object();
|
||||||
|
|
||||||
|
// Removed the duplicate key.
|
||||||
|
QCOMPARE(obj.length(), 1);
|
||||||
|
|
||||||
|
// Retained the last of the duplicates.
|
||||||
|
const QJsonValue val = obj.begin().value();
|
||||||
|
QVERIFY(val.isDouble());
|
||||||
|
QCOMPARE(val.toDouble(), 0.0);
|
||||||
|
|
||||||
|
// It should not leak.
|
||||||
|
// In particular it should not forget to deref the container for the inner object.
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_MAIN(tst_QtJson)
|
QTEST_MAIN(tst_QtJson)
|
||||||
#include "tst_qtjson.moc"
|
#include "tst_qtjson.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user