JSON: Properly handle bad objects in JSON.stringify()
For objects with circular structures we generate a proper error message and fail earlier. For objects with excessive recursion we throw a range error rather than crashing. This behavior is modeled after node's behavior in such circumstances. We use the existing stack overflow detection to determine when to throw the range error. Testing shows that on windows the limit was insufficient. Lower it. Pick-to: 6.2 6.3 6.4 Fixes: QTBUG-92192 Change-Id: I25dd302f65f359111e42492df3c71549c4ed7157 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
parent
5a8ac8f3d5
commit
af1ef35fa0
|
@ -323,6 +323,9 @@ void ExecutionEngine::initializeStaticMembers()
|
|||
#elif defined(Q_OS_ANDROID)
|
||||
// In experiments, it started crashing at 1059.
|
||||
s_maxCallDepth = 1000;
|
||||
#elif defined(Q_OS_WIN)
|
||||
// We've seen crashes around 750.
|
||||
s_maxCallDepth = 640;
|
||||
#else
|
||||
s_maxCallDepth = 1234;
|
||||
#endif
|
||||
|
|
|
@ -610,6 +610,29 @@ struct Stringify
|
|||
QString makeMember(const QString &key, const Value &v);
|
||||
};
|
||||
|
||||
class [[nodiscard]] CallDepthAndCycleChecker
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(CallDepthAndCycleChecker);
|
||||
|
||||
public:
|
||||
CallDepthAndCycleChecker(Stringify *stringify, Object *o)
|
||||
: m_callDepthRecorder(stringify->v4)
|
||||
{
|
||||
if (stringify->stackContains(o)) {
|
||||
stringify->v4->throwTypeError(
|
||||
QStringLiteral("Cannot convert circular structure to JSON"));
|
||||
}
|
||||
|
||||
qDebug() << stringify->v4->callDepth;
|
||||
stringify->v4->checkStackLimits();
|
||||
}
|
||||
|
||||
bool foundProblem() const { return m_callDepthRecorder.ee->hasException; }
|
||||
|
||||
private:
|
||||
ExecutionEngineCallDepthRecorder m_callDepthRecorder;
|
||||
};
|
||||
|
||||
static QString quote(const QString &str)
|
||||
{
|
||||
QString product;
|
||||
|
@ -740,10 +763,9 @@ QString Stringify::makeMember(const QString &key, const Value &v)
|
|||
|
||||
QString Stringify::JO(Object *o)
|
||||
{
|
||||
if (stackContains(o)) {
|
||||
v4->throwTypeError();
|
||||
CallDepthAndCycleChecker check(this, o);
|
||||
if (check.foundProblem())
|
||||
return QString();
|
||||
}
|
||||
|
||||
Scope scope(v4);
|
||||
|
||||
|
@ -800,10 +822,9 @@ QString Stringify::JO(Object *o)
|
|||
|
||||
QString Stringify::JA(Object *a)
|
||||
{
|
||||
if (stackContains(a)) {
|
||||
v4->throwTypeError();
|
||||
CallDepthAndCycleChecker check(this, a);
|
||||
if (check.foundProblem())
|
||||
return QString();
|
||||
}
|
||||
|
||||
Scope scope(a->engine());
|
||||
|
||||
|
|
|
@ -52,6 +52,9 @@ private slots:
|
|||
void writeProperty_javascriptExpression_data();
|
||||
void writeProperty_javascriptExpression();
|
||||
|
||||
void cyclicStringify();
|
||||
void recursiveStringify();
|
||||
|
||||
private:
|
||||
QByteArray readAsUtf8(const QString &fileName);
|
||||
static QJsonValue valueFromJson(const QByteArray &json);
|
||||
|
@ -489,6 +492,43 @@ void tst_qjsonbinding::writeProperty_javascriptExpression()
|
|||
QCOMPARE(ret.toString(), expectedJson);
|
||||
}
|
||||
|
||||
void tst_qjsonbinding::cyclicStringify()
|
||||
{
|
||||
QJSEngine e;
|
||||
const QString program = QStringLiteral(R"(
|
||||
var a = {};
|
||||
a.a = a;
|
||||
JSON.stringify(a);
|
||||
)");
|
||||
|
||||
QJSValue result = e.evaluate(program);
|
||||
QVERIFY(result.isError());
|
||||
QCOMPARE(result.errorType(), QJSValue::TypeError);
|
||||
QVERIFY(result.toString().contains(QLatin1String("Cannot convert circular structure to JSON")));
|
||||
}
|
||||
|
||||
void tst_qjsonbinding::recursiveStringify()
|
||||
{
|
||||
QJSEngine e;
|
||||
const QString program = QStringLiteral(R"(
|
||||
function create() {
|
||||
var a = {}
|
||||
Object.defineProperty(a, "a", {
|
||||
enumerable: true,
|
||||
get: function() { return create(); }
|
||||
});
|
||||
return a;
|
||||
}
|
||||
|
||||
JSON.stringify(create());
|
||||
)");
|
||||
|
||||
QJSValue result = e.evaluate(program);
|
||||
QVERIFY(result.isError());
|
||||
QCOMPARE(result.errorType(), QJSValue::RangeError);
|
||||
QVERIFY(result.toString().contains(QLatin1String("Maximum call stack size exceeded")));
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qjsonbinding)
|
||||
|
||||
#include "tst_qjsonbinding.moc"
|
||||
|
|
Loading…
Reference in New Issue