Source code

Revision control

Copy as Markdown

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include <QtProtobuf/qprotobufpropertyordering.h>
#include <QtProtobuf/private/qprotobufpropertyorderingbuilder_p.h>
#include <QtProtobuf/private/qtprotobufdefs_p.h>
#include <QtProtobuf/private/qtprotobuflogging_p.h>
#include <QtProtobuf/qprotobufregistration.h>
#include <QtCore/q20utility.h>
#include <QtCore/qhash.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qreadwritelock.h>
#include <QtCore/qstring.h>
#include <limits>
namespace {
/*!
\internal
The ProtobufOrderingRegistry stores the mapping between
QtProtobufPrivate::QProtobufPropertyOrdering and QMetaType.
*/
struct ProtobufOrderingRegistry
{
using ProtobufOrderingRegistryRecord =
std::pair<QMetaType, QtProtobufPrivate::QProtobufPropertyOrdering>;
void registerOrdering(QMetaType type, QtProtobufPrivate::QProtobufPropertyOrdering ordering)
{
QWriteLocker locker(&m_lock);
m_registry[ordering.messageFullName().toString()] =
ProtobufOrderingRegistryRecord(type, ordering);
}
QtProtobufPrivate::QProtobufPropertyOrdering getOrdering(QMetaType type) const
{
QtProtobufPrivate::QProtobufPropertyOrdering ordering{ nullptr };
QReadLocker locker(&m_lock);
for (auto it = m_registry.constBegin(); it != m_registry.constEnd(); ++it) {
if (type == it.value().first) {
ordering = it.value().second;
break;
}
}
return ordering;
}
ProtobufOrderingRegistryRecord
findMessageByName(const QString &messageName) const
{
ProtobufOrderingRegistryRecord record = {
QMetaType(QMetaType::UnknownType), { nullptr }
};
QReadLocker locker(&m_lock);
auto it = m_registry.constFind(messageName);
if (it != m_registry.constEnd())
record = it.value();
return record;
}
private:
mutable QReadWriteLock m_lock;
QHash<QString, ProtobufOrderingRegistryRecord> m_registry;
};
Q_GLOBAL_STATIC(ProtobufOrderingRegistry, orderingRegistry)
} // namespace
QT_BEGIN_NAMESPACE
namespace QtProtobufPrivate {
static_assert(std::is_trivially_destructible_v<QProtobufPropertyOrdering>);
static_assert(std::is_same_v<std::underlying_type_t<QtProtobufPrivate::FieldFlag>, uint>);
constexpr uint jsonNameOffsetsOffset = 0;
// Use this constant to make the +/- 1 more easily readable
constexpr int NullTerminator = 1;
QUtf8StringView QProtobufPropertyOrdering::messageFullName() const
{
Q_ASSERT(data);
const char *name = char_data();
return { name, qsizetype(data->fullPackageNameSize) };
}
QUtf8StringView QProtobufPropertyOrdering::jsonName(int index) const
{
Q_ASSERT(data);
if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
return {}; // Invalid index (out of bounds)
const uint stringOffset = uint_dataForIndex(index, jsonNameOffsetsOffset);
const char *name = char_data() + stringOffset;
// This is fine because we store an extra offset for end-of-string
const uint nameLength =
uint_dataForIndex(index + 1, jsonNameOffsetsOffset) - NullTerminator - stringOffset;
return { name, qsizetype(nameLength) };
}
int QProtobufPropertyOrdering::fieldNumber(int index) const
{
Q_ASSERT(data);
if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
return -1; // Invalid index (out of bounds)
uint fieldNumber = uint_dataForIndex(index, data->fieldNumberOffset);
if (Q_UNLIKELY(fieldNumber > uint(std::numeric_limits<int>::max())))
return -1;
return int(fieldNumber);
}
int QProtobufPropertyOrdering::propertyIndex(int index) const
{
Q_ASSERT(data);
if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
return -1; // Invalid index (out of bounds)
uint propertyIndex = uint_dataForIndex(index, data->propertyIndexOffset);
if (Q_UNLIKELY(propertyIndex > uint(std::numeric_limits<int>::max())))
return -1;
return int(propertyIndex);
}
int QProtobufPropertyOrdering::indexOfFieldNumber(int fieldNumber) const
{
Q_ASSERT(data);
if (Q_LIKELY(fieldNumber > 0)) {
for (int i = 0; i < fieldCount(); ++i) {
if (uint_dataForIndex(i, data->fieldNumberOffset) == static_cast<uint>(fieldNumber))
return i;
}
}
return -1;
}
FieldFlags QProtobufPropertyOrdering::fieldFlags(int index) const
{
Q_ASSERT(data);
if (Q_UNLIKELY(index < 0 || index >= fieldCount()))
return {}; // Invalid index (out of bounds)
return FieldFlags { int(uint_dataForIndex(index, data->flagsOffset)) };
}
/*!
\internal
Access the generated uint* metadata of message properties.
[ Data, uint_data , char_data ] > Message_metadata
[ . . , ^ x x x x , . . . . . ]
*/
uint *QProtobufPropertyOrdering::uint_data(NonConstTag /*unused*/) const
{
Q_ASSERT(data);
Q_ASSERT(data->version == 0);
quintptr dataPtr = quintptr(data);
dataPtr += sizeof(Data);
#if 0 // if Data is ever not just a bunch of uints, remove the #if 0
if (dataPtr % alignof(uint) != 0)
dataPtr += alignof(uint) - (dataPtr % alignof(uint));
#else
static_assert(alignof(Data) == alignof(uint));
#endif
return reinterpret_cast<uint *>(dataPtr);
}
const uint *QProtobufPropertyOrdering::uint_data() const
{
return uint_data(NonConstTag{});
}
/*!
\internal
Access the generated char* metadata of message properties.
[ Data, uint_data , char_data ] > Message_metadata
[ . . , . . . . . , ^ x x x x ]
*/
char *QProtobufPropertyOrdering::char_data(NonConstTag /*unused*/) const
{
Q_ASSERT(data);
uint* uintDataPtr = uint_data(NonConstTag{});
// char_data starts after the last field flag (flagsOffset + numFields)
uint charDataOffset = data->flagsOffset + data->numFields;
return reinterpret_cast<char *>(uintDataPtr + charDataOffset);
}
const char *QProtobufPropertyOrdering::char_data() const
{
return char_data(NonConstTag{});
}
const uint &QProtobufPropertyOrdering::uint_dataForIndex(int index, uint offset) const
{
Q_ASSERT(data);
Q_ASSERT(index >= 0);
Q_ASSERT(uint(index) < data->numFields
|| (offset == jsonNameOffsetsOffset && uint(index) == data->numFields));
return *(uint_data() + offset + index);
}
void registerOrdering(QMetaType type, QProtobufPropertyOrdering ordering)
{
orderingRegistry->registerOrdering(type, ordering);
}
QProtobufPropertyOrdering getOrderingByMetaType(QMetaType type)
{
return orderingRegistry->getOrdering(type);
}
QProtobufMessagePointer constructMessageByName(const QString &messageType)
{
qRegisterProtobufTypes();
ProtobufOrderingRegistry::ProtobufOrderingRegistryRecord messageOrderingRecord =
orderingRegistry->findMessageByName(messageType);
QMetaType type = messageOrderingRecord.first;
QProtobufMessagePointer pointer;
if (type.id() != QMetaType::UnknownType) {
void *msg = type.create();
pointer.reset(static_cast<QProtobufMessage *>(msg));
return pointer;
}
qProtoWarning() << "Unable to find protobuf message with name" << messageType
<< ". Message is not registered.";
return pointer;
}
class QProtobufPropertyOrderingBuilderPrivate
{
public:
struct FieldDefinition
{
QByteArray jsonName;
uint fieldNumber;
uint propertyIndex;
FieldFlags flags;
};
std::vector<FieldDefinition> fields;
QByteArray packageName;
};
QProtobufPropertyOrderingBuilder::QProtobufPropertyOrderingBuilder(QByteArray packageName)
: d_ptr(new QProtobufPropertyOrderingBuilderPrivate())
{
d_ptr->packageName = std::move(packageName);
}
QProtobufPropertyOrderingBuilder::~QProtobufPropertyOrderingBuilder()
{
delete d_ptr;
}
void QProtobufPropertyOrderingBuilder::addV0Field(QByteArray jsonName, uint fieldNumber,
uint propertyIndex, FieldFlags flags)
{
Q_D(QProtobufPropertyOrderingBuilder);
Q_ASSERT_X(fieldNumber <= ProtobufFieldNumMax && fieldNumber >= ProtobufFieldNumMin,
"QProtobufPropertyOrderingBuilder::addV0Field",
"Field number is out of the valid field number range.");
Q_ASSERT(d->fields.size() < ProtobufFieldMaxCount);
d->fields.push_back({ std::move(jsonName), fieldNumber, propertyIndex, flags });
}
/*!
\internal
Builds the QProtobufPropertyOrdering object from the builder data.
The Data struct must be manually destructed (var->~Data()) and then
free()d by the caller.
*/
QProtobufPropertyOrdering::Data *QProtobufPropertyOrderingBuilder::build() const
{
Q_D(const QProtobufPropertyOrderingBuilder);
if (d->fields.size() > ProtobufFieldMaxCount)
return nullptr;
qsizetype charSpaceNeeded = NullTerminator + d->packageName.size() + NullTerminator;
for (const auto &field : d->fields)
charSpaceNeeded += field.jsonName.size() + NullTerminator;
const size_t uintSpaceNeeded = sizeof(QProtobufPropertyOrdering::Data)
+ d->fields.size() * sizeof(uint) * 4 + sizeof(uint) /* eos marker offset */;
static_assert(sizeof(QProtobufPropertyOrdering::Data) == 24,
"Data size changed, update builder code");
const size_t spaceNeeded = uintSpaceNeeded + charSpaceNeeded;
auto *storage = static_cast<char *>(calloc(1, spaceNeeded));
auto *data = new (storage) QProtobufPropertyOrdering::Data();
auto raii = qScopeGuard([data] { data->~Data(); free(data); });
data->version = 0u;
data->numFields = uint(d->fields.size());
data->fieldNumberOffset = uint(d->fields.size() * 1 + 1);
data->propertyIndexOffset = uint(d->fields.size() * 2 + 1);
data->flagsOffset = uint(d->fields.size() * 3 + 1);
data->fullPackageNameSize = uint(d->packageName.size());
using NonConstTag = QProtobufPropertyOrdering::NonConstTag;
QProtobufPropertyOrdering ordering{data};
uint *uintData = ordering.uint_data(NonConstTag{});
uint jsonArrayOffset = data->fullPackageNameSize + NullTerminator;
for (uint i = 0; i < data->numFields; ++i) {
const auto &field = d->fields[i];
if (field.fieldNumber > ProtobufFieldNumMax || field.fieldNumber < ProtobufFieldNumMin)
return nullptr;
uintData[i] = jsonArrayOffset;
jsonArrayOffset += field.jsonName.size() + NullTerminator;
uintData[i + data->fieldNumberOffset] = field.fieldNumber;
uintData[i + data->propertyIndexOffset] = field.propertyIndex;
uintData[i + data->flagsOffset] = uint(field.flags.toInt());
}
uintData[d->fields.size()] = jsonArrayOffset;
Q_ASSERT(q20::cmp_equal(jsonArrayOffset + NullTerminator, charSpaceNeeded));
char *charData = ordering.char_data(NonConstTag{});
[[maybe_unused]]
char * const charStart = charData;
charData = std::copy_n(d->packageName.constData(), d->packageName.size() + NullTerminator,
charData);
for (auto &field : d->fields) {
charData = std::copy_n(field.jsonName.constData(), field.jsonName.size() + NullTerminator,
charData);
}
charData[0] = '\0'; // Empty string at the end of the char array
++charData; // Trailing null terminator
Q_ASSERT(std::distance(charStart, charData) == charSpaceNeeded);
Q_ASSERT(quintptr(charData) - quintptr(data) == quintptr(spaceNeeded));
raii.dismiss();
return data;
}
} // namespace QtProtobufPrivate
QT_END_NAMESPACE