Fix crashes when creating large documents

Compact an object in regular intervals when inserting
data into it, to avoid the object becoming huge.
Compact an object/array before inserting into another
array or object.
Check that the document doesn't get so big it's overflowing
the internal data structures.

Task-number: QTBUG-29288
Change-Id: Id39d80dac1e7d5a11f40819f41b4b336bce16947
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Lars Knoll 2013-02-15 10:44:54 +01:00 committed by The Qt Project
parent 9cc106d9d7
commit e1d3687d64
6 changed files with 64 additions and 16 deletions

View File

@ -149,6 +149,10 @@ bool Data::valid() const
int Base::reserveSpace(uint dataSize, int posInTable, uint numItems, bool replace)
{
Q_ASSERT(posInTable >= 0 && posInTable <= (int)length);
if (size + dataSize >= Value::MaxSize) {
qWarning("QJson: Document too large to store in data structure %d %d %d", (uint)size, dataSize, Value::MaxSize);
return 0;
}
offset off = tableOffset;
// move table to new position
@ -334,7 +338,7 @@ bool Value::isValid(const Base *b) const
/*!
\internal
*/
int Value::requiredStorage(const QJsonValue &v, bool *compressed)
int Value::requiredStorage(QJsonValue &v, bool *compressed)
{
*compressed = false;
switch (v.t) {
@ -351,6 +355,11 @@ int Value::requiredStorage(const QJsonValue &v, bool *compressed)
}
case QJsonValue::Array:
case QJsonValue::Object:
if (v.d && v.d->compactionCounter) {
v.detach();
v.d->compact();
v.base = static_cast<QJsonPrivate::Base *>(v.d->header->root());
}
return v.base ? v.base->size : sizeof(QJsonPrivate::Base);
case QJsonValue::Undefined:
case QJsonValue::Null:

View File

@ -543,6 +543,9 @@ public:
class Value
{
public:
enum {
MaxSize = (1<<27) - 1
};
union {
uint _dummy;
qle_bitfield<0, 3> type;
@ -564,7 +567,7 @@ public:
bool isValid(const Base *b) const;
static int requiredStorage(const QJsonValue &v, bool *compressed);
static int requiredStorage(QJsonValue &v, bool *compressed);
static uint valueToStore(const QJsonValue &v, uint offset);
static void copyData(const QJsonValue &v, char *dest, bool compressed);
};

View File

@ -391,9 +391,10 @@ QJsonValue QJsonArray::takeAt(int i)
void QJsonArray::insert(int i, const QJsonValue &value)
{
Q_ASSERT (i >= 0 && i <= (a ? (int)a->length : 0));
QJsonValue val = value;
bool compressed;
int valueSize = QJsonPrivate::Value::requiredStorage(value, &compressed);
int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed);
detach(valueSize + sizeof(QJsonPrivate::Value));
@ -401,13 +402,16 @@ void QJsonArray::insert(int i, const QJsonValue &value)
a->tableOffset = sizeof(QJsonPrivate::Array);
int valueOffset = a->reserveSpace(valueSize, i, 1, false);
if (!valueOffset)
return;
QJsonPrivate::Value &v = (*a)[i];
v.type = (value.t == QJsonValue::Undefined ? QJsonValue::Null : value.t);
v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t);
v.latinOrIntValue = compressed;
v.latinKey = false;
v.value = QJsonPrivate::Value::valueToStore(value, valueOffset);
v.value = QJsonPrivate::Value::valueToStore(val, valueOffset);
if (valueSize)
QJsonPrivate::Value::copyData(value, (char *)a + valueOffset, compressed);
QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed);
}
/*!
@ -437,9 +441,10 @@ void QJsonArray::insert(int i, const QJsonValue &value)
void QJsonArray::replace(int i, const QJsonValue &value)
{
Q_ASSERT (a && i >= 0 && i < (int)(a->length));
QJsonValue val = value;
bool compressed;
int valueSize = QJsonPrivate::Value::requiredStorage(value, &compressed);
int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed);
detach(valueSize);
@ -447,13 +452,16 @@ void QJsonArray::replace(int i, const QJsonValue &value)
a->tableOffset = sizeof(QJsonPrivate::Array);
int valueOffset = a->reserveSpace(valueSize, i, 1, true);
if (!valueOffset)
return;
QJsonPrivate::Value &v = (*a)[i];
v.type = (value.t == QJsonValue::Undefined ? QJsonValue::Null : value.t);
v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t);
v.latinOrIntValue = compressed;
v.latinKey = false;
v.value = QJsonPrivate::Value::valueToStore(value, valueOffset);
v.value = QJsonPrivate::Value::valueToStore(val, valueOffset);
if (valueSize)
QJsonPrivate::Value::copyData(value, (char *)a + valueOffset, compressed);
QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed);
++d->compactionCounter;
if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u)

View File

@ -67,7 +67,8 @@ struct Q_CORE_EXPORT QJsonParseError
IllegalUTF8String,
UnterminatedString,
MissingObject,
DeepNesting
DeepNesting,
DocumentTooLarge
};
QString errorString() const;

View File

@ -317,9 +317,10 @@ QJsonObject::iterator QJsonObject::insert(const QString &key, const QJsonValue &
remove(key);
return end();
}
QJsonValue val = value;
bool latinOrIntValue;
int valueSize = QJsonPrivate::Value::requiredStorage(value, &latinOrIntValue);
int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue);
bool latinKey = QJsonPrivate::useCompressed(key);
int valueOffset = sizeof(QJsonPrivate::Entry) + QJsonPrivate::qStringSize(key, latinKey);
@ -335,16 +336,21 @@ QJsonObject::iterator QJsonObject::insert(const QString &key, const QJsonValue &
if (keyExists)
++d->compactionCounter;
o->reserveSpace(requiredSize, pos, 1, keyExists);
uint off = o->reserveSpace(requiredSize, pos, 1, keyExists);
if (!off)
return end();
QJsonPrivate::Entry *e = o->entryAt(pos);
e->value.type = value.t;
e->value.type = val.t;
e->value.latinKey = latinKey;
e->value.latinOrIntValue = latinOrIntValue;
e->value.value = QJsonPrivate::Value::valueToStore(value, (char *)e - (char *)o + valueOffset);
e->value.value = QJsonPrivate::Value::valueToStore(val, (char *)e - (char *)o + valueOffset);
QJsonPrivate::copyString((char *)(e + 1), key, latinKey);
if (valueSize)
QJsonPrivate::Value::copyData(value, (char *)e + valueOffset, latinOrIntValue);
QJsonPrivate::Value::copyData(val, (char *)e + valueOffset, latinOrIntValue);
if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u)
compact();
return iterator(this, pos);
}

View File

@ -76,6 +76,7 @@ QT_BEGIN_NAMESPACE
#define JSONERR_UTERM_STR QT_TRANSLATE_NOOP("QJsonParseError", "unterminated string")
#define JSONERR_MISS_OBJ QT_TRANSLATE_NOOP("QJsonParseError", "object is missing after a comma")
#define JSONERR_DEEP_NEST QT_TRANSLATE_NOOP("QJsonParseError", "too deeply nested document")
#define JSONERR_DOC_LARGE QT_TRANSLATE_NOOP("QJsonParseError", "too large document")
/*!
\class QJsonParseError
@ -105,6 +106,7 @@ QT_BEGIN_NAMESPACE
\value UnterminatedString A string wasn't terminated with a quote
\value MissingObject An object was expected but couldn't be found
\value DeepNesting The JSON document is too deeply nested for the parser to parse it
\value DocumentTooLarge The JSON document is too large for the parser to parse it
*/
/*!
@ -173,6 +175,9 @@ QString QJsonParseError::errorString() const
case DeepNesting:
sz = JSONERR_DEEP_NEST;
break;
case DocumentTooLarge:
sz = JSONERR_DOC_LARGE;
break;
}
#ifndef QT_BOOTSTRAPPED
return QCoreApplication::translate("QJsonParseError", sz);
@ -579,6 +584,10 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset)
return false;
case Quote: {
val->type = QJsonValue::String;
if (current - baseOffset >= Value::MaxSize) {
lastError = QJsonParseError::DocumentTooLarge;
return false;
}
val->value = current - baseOffset;
bool latin1;
if (!parseString(&latin1))
@ -590,6 +599,10 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset)
}
case BeginArray:
val->type = QJsonValue::Array;
if (current - baseOffset >= Value::MaxSize) {
lastError = QJsonParseError::DocumentTooLarge;
return false;
}
val->value = current - baseOffset;
if (!parseArray())
return false;
@ -598,6 +611,10 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset)
return true;
case BeginObject:
val->type = QJsonValue::Object;
if (current - baseOffset >= Value::MaxSize) {
lastError = QJsonParseError::DocumentTooLarge;
return false;
}
val->value = current - baseOffset;
if (!parseObject())
return false;
@ -707,6 +724,10 @@ bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset)
int pos = reserveSpace(sizeof(double));
*(quint64 *)(data + pos) = qToLittleEndian(ui);
if (current - baseOffset >= Value::MaxSize) {
lastError = QJsonParseError::DocumentTooLarge;
return false;
}
val->value = pos - baseOffset;
val->latinOrIntValue = false;