From 7cb5b324f05b3d9e530856bd0eb2ddf4e34b2f55 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 14 Jul 2018 16:42:08 -0700 Subject: QCborValue: move the toDiagnosticNotation() function to its own file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we ever need to add QCborValue to the bootstrap library, it's unlikely that we'll need this part. And by splitting it, I can make the code handle more cases, that hadn't been properly handled before. Change-Id: I2f630efbbce54f14bfa9fffd154160c0ad893695 Reviewed-by: MÃ¥rten Nordheim Reviewed-by: Edward Welbourne --- src/corelib/global/qnumeric_p.h | 52 +++++ src/corelib/serialization/qcbordiagnostic.cpp | 281 ++++++++++++++++++++++++++ src/corelib/serialization/qcborvalue.cpp | 225 --------------------- src/corelib/serialization/qcborvalue_p.h | 2 + src/corelib/serialization/serialization.pri | 1 + 5 files changed, 336 insertions(+), 225 deletions(-) create mode 100644 src/corelib/serialization/qcbordiagnostic.cpp (limited to 'src/corelib') diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index 5f8a124bcc..9c8514f5a3 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -163,6 +163,58 @@ Q_DECL_CONST_FUNCTION static inline bool qt_is_finite(float f) #ifndef Q_CLANG_QDOC namespace { +/*! + Returns true if the double \a v can be converted to type \c T, false if + it's out of range. If the conversion is successful, the converted value is + stored in \a value; if it was not successful, \a value will contain the + minimum or maximum of T, depending on the sign of \a d. If \c T is + unsigned, then \a value contains the absolute value of \a v. + + This function works for v containing infinities, but not NaN. It's the + caller's responsibility to exclude that possibility before calling it. +*/ +template static inline bool convertDoubleTo(double v, T *value) +{ + Q_STATIC_ASSERT(std::numeric_limits::is_integer); + + // The [conv.fpint] (7.10 Floating-integral conversions) section of the C++ + // standard says only exact conversions are guaranteed. Converting + // integrals to floating-point with loss of precision has implementation- + // defined behavior whether the next higher or next lower is returned; + // converting FP to integral is UB if it can't be represented. + // + // That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be + // correct, but Clang, ICC and MSVC don't realize that it's a constant and + // the math call stays in the compiled code. + + double supremum; + if (std::numeric_limits::is_signed) { + supremum = -1.0 * std::numeric_limits::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64) + *value = std::numeric_limits::min(); + if (v < std::numeric_limits::min()) + return false; + } else { + using ST = typename std::make_signed::type; + supremum = -2.0 * std::numeric_limits::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64) + v = fabs(v); + } + + *value = std::numeric_limits::max(); + if (v >= supremum) + return false; + + // Now we can convert, these two conversions cannot be UB + *value = T(v); + +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wfloat-equal") +QT_WARNING_DISABLE_CLANG("-Wfloat-equal") + + return *value == v; + +QT_WARNING_POP +} + // Overflow math. // This provides efficient implementations for int, unsigned, qsizetype and // size_t. Implementations for 8- and 16-bit types will work but may not be as diff --git a/src/corelib/serialization/qcbordiagnostic.cpp b/src/corelib/serialization/qcbordiagnostic.cpp new file mode 100644 index 0000000000..8e641ff7cf --- /dev/null +++ b/src/corelib/serialization/qcbordiagnostic.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcborvalue.h" +#include "qcborvalue_p.h" + +#include "qcborarray.h" +#include "qcbormap.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { +class DiagnosticNotation +{ +public: + static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts) + { + DiagnosticNotation dn(opts); + dn.appendValue(v); + return dn.result; + } + +private: + QStack byteArrayFormatStack; + QString separator; + QString result; + QCborValue::DiagnosticNotationOptions opts; + int nestingLevel = 0; + + struct Nest { + enum { IndentationWidth = 4 }; + DiagnosticNotation *dn; + Nest(DiagnosticNotation *that) : dn(that) + { + ++dn->nestingLevel; + static const char indent[IndentationWidth + 1] = " "; + if (dn->opts & QCborValue::LineWrapped) + dn->separator += QLatin1String(indent, IndentationWidth); + } + ~Nest() + { + --dn->nestingLevel; + if (dn->opts & QCborValue::LineWrapped) + dn->separator.chop(IndentationWidth); + } + }; + + DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_) + : separator(QLatin1String(opts_ & QCborValue::LineWrapped ? "\n" : "")), opts(opts_) + { + byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16)); + } + + void appendString(const QString &s); + void appendArray(const QCborArray &a); + void appendMap(const QCborMap &m); + void appendValue(const QCborValue &v); +}; +} + +static QString makeFpString(double d) +{ + QString s; + quint64 v; + if (qt_is_inf(d)) { + s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf"); + } else if (qt_is_nan(d)) { + s = QStringLiteral("nan"); + } else if (convertDoubleTo(d, &v)) { + s = QString::fromLatin1("%1.0").arg(v); + if (d < 0) + s.prepend(QLatin1Char('-')); + } else { + s = QString::number(d, 'g', QLocale::FloatingPointShortest); + if (!s.contains(QLatin1Char('.')) && !s.contains('e')) + s += QLatin1Char('.'); + } + return s; +} + +static bool isByteArrayEncodingTag(QCborTag tag) +{ + switch (quint64(tag)) { + case quint64(QCborKnownTags::ExpectedBase16): + case quint64(QCborKnownTags::ExpectedBase64): + case quint64(QCborKnownTags::ExpectedBase64url): + return true; + } + return false; +} + +void DiagnosticNotation::appendString(const QString &s) +{ + result += QLatin1Char('"') + + QString(s) + .replace(QLatin1Char('\\'), QLatin1String("\\\\")) + .replace(QLatin1Char('"'), QLatin1String("\\\"")) + + QLatin1Char('"'); +} + +void DiagnosticNotation::appendArray(const QCborArray &a) +{ + result += QLatin1Char('['); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : a) { + result += comma + separator; + comma = commaValue; + appendValue(v); + } + } + + result += separator + QLatin1Char(']'); +} + +void DiagnosticNotation::appendMap(const QCborMap &m) +{ + result += QLatin1Char('{'); + + // length 2 (including the space) when not line wrapping + QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2); + { + Nest n(this); + QLatin1String comma; + for (auto v : m) { + result += comma + separator; + comma = commaValue; + appendValue(v.first); + result += QLatin1String(": "); + appendValue(v.second); + } + } + + result += separator + QLatin1Char('}'); +}; + +void DiagnosticNotation::appendValue(const QCborValue &v) +{ + switch (v.type()) { + case QCborValue::Integer: + result += QString::number(v.toInteger()); + return; + case QCborValue::ByteArray: + switch (byteArrayFormatStack.top()) { + case int(QCborKnownTags::ExpectedBase16): + result += QString::fromLatin1("h'" + + v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') + + '\''); + return; + case int(QCborKnownTags::ExpectedBase64): + result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\''); + return; + default: + case int(QCborKnownTags::ExpectedBase64url): + result += QString::fromLatin1("b64'" + + v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + + '\''); + return; + } + case QCborValue::String: + return appendString(v.toString()); + case QCborValue::Array: + return appendArray(v.toArray()); + case QCborValue::Map: + return appendMap(v.toMap()); + case QCborValue::False: + result += QLatin1String("false"); + return; + case QCborValue::True: + result += QLatin1String("true"); + return; + case QCborValue::Null: + result += QLatin1String("null"); + return; + case QCborValue::Undefined: + result += QLatin1String("undefined"); + return; + case QCborValue::Double: + result += makeFpString(v.toDouble()); + return; + case QCborValue::Invalid: + result += QStringLiteral(""); + return; + + default: + // Only tags, extended types, and simple types remain; see below. + break; + } + + if (v.isTag()) { + // We handle all extended types as regular tags, so it won't matter + // whether we understand that tag or not. + bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag()); + if (byteArrayFormat) + byteArrayFormatStack.push(int(v.tag())); + result += QString::number(quint64(v.tag())) + QLatin1Char('('); + appendValue(v.taggedValue()); + result += QLatin1Char(')'); + if (byteArrayFormat) + byteArrayFormatStack.pop(); + } else { + // must be a simple type + result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType())); + } +} + +/*! + Creates the diagnostic notation equivalent of this CBOR object and returns + it. The \a opts parameter controls the dialect of the notation. Diagnostic + notation is useful in debugging, to aid the developer in understanding what + value is stored in the QCborValue or in a CBOR stream. For that reason, the + Qt API provides no support for parsing the diagnostic back into the + in-memory format or CBOR stream, though the representation is unique and it + would be possible. + + CBOR diagnostic notation is specified by + \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049. + It is a text representation of the CBOR stream and it is very similar to + JSON, but it supports the CBOR types not found in JSON. The extended format + enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is + currently in some IETF drafts and its format is subject to change. + + This function produces the equivalent representation of the stream that + toCbor() would produce, without any transformation option provided there. + This also implies this function may not produce a representation of the + stream that was used to create the object, if it was created using + fromCbor(), as that function may have applied transformations. For a + high-fidelity notation of a stream, without transformation, see the \c + cbordump example. + + \sa toCbor(), toJsonDocument(), QJsonDocument::toJson() + */ +QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const +{ + return DiagnosticNotation::create(*this, opts); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 0ec84c12d1..4108418550 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -47,9 +47,7 @@ #include #include #include -#include #include -#include #include @@ -768,58 +766,6 @@ using namespace QtCbor; // in qcborstream.cpp extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error); -/*! - Returns true if the double \a v can be converted to type \c T, false if - it's out of range. If the conversion is successful, the converted value is - stored in \a value; if it was not successful, \a value will contain the - minimum or maximum of T, depending on the sign of \a d. If \c T is - unsigned, then \a value contains the absolute value of \a v. - - This function works for v containing infinities, but not NaN. It's the - caller's responsibility to exclude that possibility before calling it. -*/ -template static inline bool convertDoubleTo(double v, T *value) -{ - Q_STATIC_ASSERT(std::numeric_limits::is_integer); - - // The [conv.fpint] (7.10 Floating-integral conversions) section of the C++ - // standard says only exact conversions are guaranteed. Converting - // integrals to floating-point with loss of precision has implementation- - // defined behavior whether the next higher or next lower is returned; - // converting FP to integral is UB if it can't be represented. - // - // That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be - // correct, but only Clang, ICC and MSVC don't realize that it's a constant - // and the math call stays in the compiled code. - - double supremum; - if (std::numeric_limits::is_signed) { - supremum = -1.0 * std::numeric_limits::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64) - *value = std::numeric_limits::min(); - if (v < std::numeric_limits::min()) - return false; - } else { - using ST = typename std::make_signed::type; - supremum = -2.0 * std::numeric_limits::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64) - v = fabs(v); - } - - *value = std::numeric_limits::max(); - if (v >= supremum) - return false; - - // Now we can convert, these two conversions cannot be UB - *value = T(v); - -QT_WARNING_PUSH -QT_WARNING_DISABLE_GCC("-Wfloat-equal") -QT_WARNING_DISABLE_CLANG("-Wfloat-equal") - - return *value == v; - -QT_WARNING_POP -} - static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::EncodingOptions opt) { if (qt_is_nan(d)) { @@ -869,146 +815,6 @@ static inline int typeOrder(Element e1, Element e2) return comparable(e1) - comparable(e2); } -namespace { -class DiagnosticNotation -{ -public: - static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts) - { - DiagnosticNotation dn(opts); - return dn.createFromValue(v); - } - -private: - QStack byteArrayFormatStack; - QCborValue::DiagnosticNotationOptions opts; - int nestingLevel = 0; - - DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_) - : opts(opts_) - { - byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16)); - } - - QString createFromValue(const QCborValue &v); -}; -} - -QString DiagnosticNotation::createFromValue(const QCborValue &v) -{ - QString indent(1, QLatin1Char(' ')); - QString indented = indent; - if (opts & QCborValue::LineWrapped) { - indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); - indented = indent + QLatin1String(" "); - } - QScopedValueRollback rollback(nestingLevel); - ++nestingLevel; - - auto createFromArray = [=](const QCborArray &a) { - QString result; - QLatin1String comma; - for (auto v : a) { - result += comma + indented + createFromValue(v); - comma = QLatin1String(","); - } - return result; - }; - auto createFromMap = [=](const QCborMap &m) { - QString result; - QLatin1String comma; - for (auto v : m) { - result += comma + indented + createFromValue(v.first) + - QLatin1String(": ") + createFromValue(v.second); - comma = QLatin1String(","); - } - return result; - }; - auto makeFpString = [](double d) { - QString s; - quint64 v; - if (qt_is_inf(d)) { - s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf"); - } else if (qt_is_nan(d)) { - s = QStringLiteral("nan"); - } else if (convertDoubleTo(d, &v)) { - s = QString::fromLatin1("%1.0").arg(v); - if (d < 0) - s.prepend(QLatin1Char('-')); - } else { - s = QString::number(d, 'g', QLocale::FloatingPointShortest); - if (!s.contains(QLatin1Char('.')) && !s.contains('e')) - s += QLatin1Char('.'); - } - return s; - }; - auto isByteArrayEncodingTag = [](QCborTag tag) { - switch (quint64(tag)) { - case quint64(QCborKnownTags::ExpectedBase16): - case quint64(QCborKnownTags::ExpectedBase64): - case quint64(QCborKnownTags::ExpectedBase64url): - return true; - } - return false; - }; - - switch (v.type()) { - case QCborValue::Integer: - return QString::number(v.toInteger()); - case QCborValue::ByteArray: - switch (byteArrayFormatStack.top()) { - case int(QCborKnownTags::ExpectedBase16): - return QString::fromLatin1("h'" + - v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') + - '\''); - case int(QCborKnownTags::ExpectedBase64): - return QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\''); - default: - case int(QCborKnownTags::ExpectedBase64url): - return QString::fromLatin1("b64'" + - v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + - '\''); - } - case QCborValue::String: - // ### TODO: Needs escaping! - return QLatin1Char('"') + v.toString() + QLatin1Char('"'); - case QCborValue::Array: - return QLatin1Char('[') + createFromArray(v.toArray()) + indent + QLatin1Char(']'); - case QCborValue::Map: - return QLatin1Char('{') + createFromMap(v.toMap()) + indent + QLatin1Char('}'); - case QCborValue::False: - return QStringLiteral("false"); - case QCborValue::True: - return QStringLiteral("true"); - case QCborValue::Null: - return QStringLiteral("null"); - case QCborValue::Undefined: - return QStringLiteral("undefined"); - case QCborValue::Double: - return makeFpString(v.toDouble()); - case QCborValue::Invalid: - return QStringLiteral(""); - - case QCborValue::Tag: - case QCborValue::SimpleType: - default: // tags and other simple types that are recognized - break; // are all handled below - } - - if (v.isTag()) { - bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag()); - if (byteArrayFormat) - byteArrayFormatStack.push(int(v.tag())); - QString result = QString::fromLatin1("%1(%2)").arg(quint64(v.tag())).arg(createFromValue(v.taggedValue())); - if (byteArrayFormat) - byteArrayFormatStack.pop(); - return result; - } - - // must be a simple type - return QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType())); -} - QCborContainerPrivate::~QCborContainerPrivate() { // delete our elements @@ -2508,37 +2314,6 @@ Q_NEVER_INLINE void QCborValue::toCbor(QCborStreamWriter &writer, EncodingOption } } -/*! - Creates the diagnostic notation equivalent of this CBOR object and return - it. The \a opts parameter controls the dialect of the notation. Diagnostic - notation is useful in debugging, to aid the developer in understanding what - value is stored in the QCborValue or in a CBOR stream. For that reason, the - Qt API provides no support for parsing the diagnostic back into the - in-memory format or CBOR stream, though the representation is unique and it - would be possible. - - CBOR diagnostic notation is specified by - \l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049. - It is a text representation of the CBOR stream and it is very similar to - JSON, but it supports the CBOR types not found in JSON. The extended format - enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is - currently in some IETF drafts and its format is subject to change. - - This function produces the equivalent representation of the stream that - toCbor() would produce, without any transformation option provided there. - This also implies this function may not produce a representation of the - stream that was used to create the object, if it was created using - fromCbor(), as that function may have applied transformations. For a - high-fidelity notation of a stream, without transformation, see the \c - cbordump example. - - \sa toCbor(), toJsonDocument(), QJsonDocument::toJson() - */ -QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const -{ - return DiagnosticNotation::create(*this, opts); -} - void QCborValueRef::toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt) { concrete().toCbor(writer, opt); diff --git a/src/corelib/serialization/qcborvalue_p.h b/src/corelib/serialization/qcborvalue_p.h index 02aa05bcdb..3a28707056 100644 --- a/src/corelib/serialization/qcborvalue_p.h +++ b/src/corelib/serialization/qcborvalue_p.h @@ -56,6 +56,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE namespace QtCbor { diff --git a/src/corelib/serialization/serialization.pri b/src/corelib/serialization/serialization.pri index 73b99b8e64..4f2dc64e4f 100644 --- a/src/corelib/serialization/serialization.pri +++ b/src/corelib/serialization/serialization.pri @@ -24,6 +24,7 @@ HEADERS += \ SOURCES += \ serialization/qcborstream.cpp \ + serialization/qcbordiagnostic.cpp \ serialization/qcborvalue.cpp \ serialization/qdatastream.cpp \ serialization/qjson.cpp \ -- cgit v1.2.3