From: Philippe Proulx Date: Tue, 21 May 2024 17:57:03 +0000 (-0400) Subject: Add `ctf::src::Ctf2MetadataStreamParser` class (JSON text sequence) X-Git-Url: http://drtracing.org/?a=commitdiff_plain;h=c86bb441466f58d84f1accb3e09fdfd8c9c2e36b;p=babeltrace.git Add `ctf::src::Ctf2MetadataStreamParser` class (JSON text sequence) This patch adds `ctf::src::Ctf2MetadataStreamParser` which inherits `ctf::src::MetadataStreamParser` to implement a CTF 2 metadata stream (JSON text sequence) parser. The parser uses `Ctf2JsonAnyFragmentValReq` to validate each fragment, and also performs other validation at the JSON level. Signed-off-by: Philippe Proulx Change-Id: I29a29e589b9de998a2fadf7be96871902e3fd76a Reviewed-on: https://review.lttng.org/c/babeltrace/+/12736 --- diff --git a/src/Makefile.am b/src/Makefile.am index 383f9778..9a943ce5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -703,10 +703,26 @@ plugins_ctf_babeltrace_plugin_ctf_la_SOURCES = \ plugins/ctf/common/src/item-seq/medium.hpp \ plugins/ctf/common/src/metadata/ctf-ir.cpp \ plugins/ctf/common/src/metadata/ctf-ir.hpp \ + plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp \ + plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp \ + plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp \ + plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp \ + plugins/ctf/common/src/metadata/json/fcs-with-role.cpp \ + plugins/ctf/common/src/metadata/json/fcs-with-role.hpp \ + plugins/ctf/common/src/metadata/json/key-fc-types.cpp \ + plugins/ctf/common/src/metadata/json/key-fc-types.hpp \ + plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp \ + plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp \ + plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp \ + plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp \ plugins/ctf/common/src/metadata/json/strings.cpp \ plugins/ctf/common/src/metadata/json/strings.hpp \ + plugins/ctf/common/src/metadata/json/utils.cpp \ + plugins/ctf/common/src/metadata/json/utils.hpp \ plugins/ctf/common/src/metadata/json/val-req.cpp \ plugins/ctf/common/src/metadata/json/val-req.hpp \ + plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp \ + plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp \ plugins/ctf/common/src/metadata/metadata-stream-parser.cpp \ plugins/ctf/common/src/metadata/metadata-stream-parser.hpp \ plugins/ctf/common/src/metadata/normalize-clk-offset.cpp \ diff --git a/src/cpp-common/bt2c/make-span.hpp b/src/cpp-common/bt2c/make-span.hpp index 2270a8fb..4815d671 100644 --- a/src/cpp-common/bt2c/make-span.hpp +++ b/src/cpp-common/bt2c/make-span.hpp @@ -17,6 +17,12 @@ inline constexpr bt2s::span makeSpan(T * const ptr, const size_t count) noexc return nonstd::make_span(ptr, count); } +template +inline constexpr bt2s::span makeSpan(T *first, T *last) noexcept +{ + return nonstd::make_span(first, last); +} + } /* namespace bt2c */ #endif /* BABELTRACE_CPP_COMMON_BT2C_MAKE_SPAN_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp new file mode 100644 index 00000000..9941685e --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include "common/assert.h" +#include "cpp-common/bt2c/contains.hpp" +#include "cpp-common/bt2c/logging.hpp" +#include "cpp-common/bt2s/optional.hpp" + +#include "../ctf-ir.hpp" +#include "ctf-2-fc-builder.hpp" +#include "strings.hpp" +#include "utils.hpp" + +namespace ctf { +namespace src { +namespace { + +/* + * Creates and returns the set of unsigned integer field roles of the + * JSON unsigned integer field class value `jsonFc`. + */ +UIntFieldRoles uIntFieldRolesOfJsonUIntFc(const bt2c::JsonObjVal& jsonFc) +{ + UIntFieldRoles roles; + const auto jsonRoles = jsonFc[jsonstr::roles]; + + if (!jsonRoles) { + /* No roles */ + return roles; + } + + for (auto& jsonRole : jsonRoles->asArray()) { + auto& roleName = *jsonRole->asStr(); + + if (roleName == jsonstr::dataStreamClsId) { + roles.insert(UIntFieldRole::DataStreamClsId); + } else if (roleName == jsonstr::dataStreamId) { + roles.insert(UIntFieldRole::DataStreamId); + } else if (roleName == jsonstr::pktMagicNumber) { + roles.insert(UIntFieldRole::PktMagicNumber); + } else if (roleName == jsonstr::defClkTs) { + roles.insert(UIntFieldRole::DefClkTs); + } else if (roleName == jsonstr::discEventRecordCounterSnap) { + roles.insert(UIntFieldRole::DiscEventRecordCounterSnap); + } else if (roleName == jsonstr::pktContentLen) { + roles.insert(UIntFieldRole::PktContentLen); + } else if (roleName == jsonstr::pktTotalLen) { + roles.insert(UIntFieldRole::PktTotalLen); + } else if (roleName == jsonstr::pktEndDefClkTs) { + roles.insert(UIntFieldRole::PktEndDefClkTs); + } else if (roleName == jsonstr::pktSeqNum) { + roles.insert(UIntFieldRole::PktSeqNum); + } else { + BT_ASSERT(roleName == jsonstr::eventRecordClsId); + roles.insert(UIntFieldRole::EventRecordClsId); + } + } + + return roles; +} + +/* + * Creates and returns an integer range set (of type + * `IntRangeSet`) from the JSON integer range set + * value `jsonIntRangeSet`. + */ +template +IntRangeSet intRangeSetFromJsonIntRangeSet(const bt2c::JsonArrayVal& jsonIntRangeSet) +{ + typename IntRangeSet::Set ranges; + + for (auto& jsonRange : jsonIntRangeSet) { + auto& jsonRangeArray = jsonRange->asArray(); + + BT_ASSERT(jsonRangeArray.size() == 2); + ranges.emplace( + IntRangeSet::Range::makeTemp(rawIntValFromJsonIntVal(jsonRangeArray[0]), + rawIntValFromJsonIntVal(jsonRangeArray[1]))); + } + + return IntRangeSet {std::move(ranges)}; +} + +/* + * Creates and returns the integer field class mappings (of type + * `IntFcT::Mappings`) of the JSON integer field class value `jsonFc`. + */ +template +typename IntFcT::Mappings intFcMappingsOfJsonIntFc(const bt2c::JsonObjVal& jsonFc) +{ + typename IntFcT::Mappings mappings; + + if (const auto jsonMappings = jsonFc[jsonstr::mappings]) { + /* At least one mapping */ + for (auto& keyJsonIntRangesPair : jsonMappings->asObj()) { + mappings.insert(std::make_pair(keyJsonIntRangesPair.first, + intRangeSetFromJsonIntRangeSet( + keyJsonIntRangesPair.second->asArray()))); + } + } + + return mappings; +} + +/* + * Returns the preferred display base of the JSON integer field class + * value `jsonFc`. + */ +DispBase prefDispBaseOfJsonIntFc(const bt2c::JsonObjVal& jsonFc) noexcept +{ + return static_cast(jsonFc.rawVal(jsonstr::prefDispBase, 10ULL)); +} + +/* + * Creates and returns a fixed-length integer field class from the JSON + * fixed-length integer field class value `jsonFc` and the + * other parameters. + */ +Fc::UP fcFromJsonFixedLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, + const unsigned int align, const bt2c::DataLen len, + const ByteOrder byteOrder, const BitOrder bitOrder, OptAttrs&& attrs) +{ + /* Preferred display base */ + const auto prefDispBase = prefDispBaseOfJsonIntFc(jsonFc); + + /* Create field class */ + if (type == jsonstr::fixedLenUInt) { + return createFixedLenUIntFc(jsonFc.loc(), align, len, byteOrder, bitOrder, prefDispBase, + intFcMappingsOfJsonIntFc(jsonFc), + uIntFieldRolesOfJsonUIntFc(jsonFc), std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::fixedLenSInt); + return createFixedLenSIntFc(jsonFc.loc(), align, len, byteOrder, bitOrder, prefDispBase, + intFcMappingsOfJsonIntFc(jsonFc), + std::move(attrs)); + } +} + +/* + * Returns the length of the JSON field class value `jsonFc`. + */ +unsigned long long lenOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept +{ + return jsonFc.rawUIntVal(jsonstr::len); +} + +/* + * Creates and returns the fixed-length bit map field class flags (of type + * of the JSON integer field class value `jsonFc`. + */ +FixedLenBitMapFc::Flags fixedLenBitMapFlagsOfJsonFixedLenBitMapFc(const bt2c::JsonObjVal& jsonFc) +{ + using Val = FixedLenBitMapFc::Flags::value_type::second_type::Val; + + FixedLenBitMapFc::Flags flags; + const auto jsonFlags = jsonFc[jsonstr::flags]; + + for (auto& keyJsonIntRangesPair : jsonFlags->asObj()) { + flags.insert(std::make_pair( + keyJsonIntRangesPair.first, + intRangeSetFromJsonIntRangeSet(keyJsonIntRangesPair.second->asArray()))); + } + + return flags; +} + +/* + * Creates and returns a fixed-length bit array field class from the + * JSON fixed-length bit array field class value `jsonFc` and the other + * parameters. + */ +Fc::UP fcFromJsonFixedLenBitArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, + OptAttrs&& attrs) +{ + /* Alignment */ + const auto align = jsonFc.rawVal(jsonstr::align, 1ULL); + + /* Length */ + const auto len = bt2c::DataLen::fromBits(lenOfJsonFc(jsonFc)); + + /* Byte order */ + const auto byteOrder = jsonFc.rawStrVal(jsonstr::byteOrder) == jsonstr::littleEndian ? + ByteOrder::Little : + ByteOrder::Big; + + /* Bit order */ + const auto bitOrder = bt2c::call([&jsonFc, byteOrder] { + const auto bitOrderStr = optStrOfObj(jsonFc, jsonstr::bitOrder); + + if (!bitOrderStr) { + return byteOrder == ByteOrder::Little ? BitOrder::FirstToLast : BitOrder::LastToFirst; + } + + return *bitOrderStr == jsonstr::ftl ? BitOrder::FirstToLast : BitOrder::LastToFirst; + }); + + /* Create field class */ + if (type == jsonstr::fixedLenBitArray) { + return createFixedLenBitArrayFc(jsonFc.loc(), align, len, byteOrder, bitOrder, + std::move(attrs)); + } else if (type == jsonstr::fixedLenBitMap) { + return createFixedLenBitMapFc(jsonFc.loc(), align, len, byteOrder, + fixedLenBitMapFlagsOfJsonFixedLenBitMapFc(jsonFc), bitOrder, + std::move(attrs)); + } else if (type == jsonstr::fixedLenBool) { + return createFixedLenBoolFc(jsonFc.loc(), align, len, byteOrder, bitOrder, + std::move(attrs)); + } else if (type == jsonstr::fixedLenUInt || type == jsonstr::fixedLenSInt) { + return fcFromJsonFixedLenIntFc(jsonFc, type, align, len, byteOrder, bitOrder, + std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::fixedLenFloat); + return createFixedLenFloatFc(jsonFc.loc(), align, len, byteOrder, bitOrder, + std::move(attrs)); + } +} + +/* + * Creates and returns a variable-length integer field class from the + * JSON variable-length integer field class value `jsonFc` and the + * other parameters. + */ +Fc::UP fcFromJsonVarLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, + OptAttrs&& attrs) +{ + /* Preferred display base */ + const auto prefDispBase = prefDispBaseOfJsonIntFc(jsonFc); + + if (type == jsonstr::varLenUInt) { + return createVarLenUIntFc(jsonFc.loc(), prefDispBase, + intFcMappingsOfJsonIntFc(jsonFc), + uIntFieldRolesOfJsonUIntFc(jsonFc), std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::varLenSInt); + return createVarLenSIntFc(jsonFc.loc(), prefDispBase, + intFcMappingsOfJsonIntFc(jsonFc), std::move(attrs)); + } +} + +/* + * Creates and returns the field location of the JSON field class value + * `jsonFc` from its `key` property. + */ +FieldLoc fieldLocOfJsonFc(const bt2c::JsonObjVal& jsonFc, const std::string& key) +{ + auto& jsonLoc = jsonFc[key]->asObj(); + + /* Origin (scope) */ + const auto origin = bt2c::call([&jsonLoc]() -> bt2s::optional { + const auto jsonOrig = jsonLoc[jsonstr::origin]; + + if (!jsonOrig) { + return bt2s::nullopt; + } + + auto& scopeName = *jsonOrig->asStr(); + + if (scopeName == jsonstr::pktHeader) { + return Scope::PktHeader; + } else if (scopeName == jsonstr::pktCtx) { + return Scope::PktCtx; + } else if (scopeName == jsonstr::eventRecordHeader) { + return Scope::EventRecordHeader; + } else if (scopeName == jsonstr::eventRecordCommonCtx) { + return Scope::CommonEventRecordCtx; + } else if (scopeName == jsonstr::eventRecordSpecCtx) { + return Scope::SpecEventRecordCtx; + } else { + BT_ASSERT(scopeName == jsonstr::eventRecordPayload); + return Scope::EventRecordPayload; + } + }); + + /* Path */ + FieldLoc::Items items; + + { + auto& jsonPath = jsonLoc[jsonstr::path]->asArray(); + + for (auto& jsonItem : jsonPath) { + if (jsonItem->isNull()) { + /* `null` becomes `bt2s::nullopt` */ + items.emplace_back(bt2s::nullopt); + } else { + items.push_back(*jsonItem->asStr()); + } + } + } + + /* Create field location */ + return createFieldLoc(jsonFc.loc(), origin, std::move(items)); +} + +/* + * Creates and returns a string field class from the JSON string field + * class value `jsonFc` and the other parameters. + */ +Fc::UP fcFromJsonStrFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, OptAttrs&& attrs) +{ + const auto encoding = bt2c::call([&jsonFc] { + const auto encodingStr = optStrOfObj(jsonFc, jsonstr::encoding); + + if (!encodingStr || encodingStr == jsonstr::utf8) { + return StrEncoding::Utf8; + } else if (encodingStr == jsonstr::utf16Be) { + return StrEncoding::Utf16Be; + } else if (encodingStr == jsonstr::utf16Le) { + return StrEncoding::Utf16Le; + } else if (encodingStr == jsonstr::utf32Be) { + return StrEncoding::Utf32Be; + } else { + BT_ASSERT(encodingStr == jsonstr::utf32Le); + return StrEncoding::Utf32Le; + } + }); + + if (type == jsonstr::nullTerminatedStr) { + return createNullTerminatedStrFc(jsonFc.loc(), encoding, std::move(attrs)); + } else if (type == jsonstr::staticLenStr) { + return createStaticLenStrFc(jsonFc.loc(), lenOfJsonFc(jsonFc), encoding, std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::dynLenStr); + return createDynLenStrFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc), + encoding, std::move(attrs)); + } +} + +/* + * Creates and returns a static-length BLOB field class from the JSON + * static-length BLOB field class value `jsonFc` and the other + * parameters. + */ +Fc::UP fcFromJsonStaticLenBlobFc(const bt2c::JsonObjVal& jsonFc, const char * const mediaType, + OptAttrs&& attrs) +{ + /* Has metadata stream UUID role? */ + const auto jsonRoles = jsonFc[jsonstr::roles]; + const auto hasMetadataStreamUuidRole = jsonRoles && jsonRoles->asArray().size() > 0; + + /* Create field class */ + return createStaticLenBlobFc(jsonFc.loc(), lenOfJsonFc(jsonFc), mediaType, + hasMetadataStreamUuidRole, std::move(attrs)); +} + +/* + * Creates and returns a BLOB field class from the JSON BLOB field class + * value `jsonFc` and the other parameters. + */ +Fc::UP fcFromJsonBlobFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, OptAttrs&& attrs) +{ + /* Media type */ + const auto mediaType = jsonFc.rawVal(jsonstr::mediaType, ir::defaultBlobMediaType); + + /* Create field class */ + if (type == jsonstr::staticLenBlob) { + return fcFromJsonStaticLenBlobFc(jsonFc, mediaType, std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::dynLenBlob); + return createDynLenBlobFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc), + mediaType, std::move(attrs)); + } +} + +/* + * Returns the minimum alignment of the JSON field class value `jsonFc`. + */ +unsigned long long minAlignOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept +{ + return jsonFc.rawVal(jsonstr::minAlign, 1ULL); +} + +} /* namespace */ + +Ctf2FcBuilder::Ctf2FcBuilder(const bt2c::Logger& parentLogger) : + _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-FC-BUILDER"} +{ +} + +Fc::UP Ctf2FcBuilder::buildFcFromJsonVal(const bt2c::JsonVal& jsonFc) const +{ + if (jsonFc.isStr()) { + /* Field class alias reference */ + return this->_aliasedFc(*jsonFc.asStr(), jsonFc.loc()); + } + + auto& jsonFcObj = jsonFc.asObj(); + + /* Type */ + auto& type = jsonFcObj.rawStrVal(jsonstr::type); + + /* Attributes */ + auto attrs = attrsOfObj(jsonFcObj); + + /* Defer to specific method */ + if (type == jsonstr::fixedLenBitArray || type == jsonstr::fixedLenBitMap || + type == jsonstr::fixedLenBool || type == jsonstr::fixedLenUInt || + type == jsonstr::fixedLenSInt || type == jsonstr::fixedLenFloat) { + return fcFromJsonFixedLenBitArrayFc(jsonFcObj, type, std::move(attrs)); + } else if (type == jsonstr::varLenUInt || type == jsonstr::varLenSInt) { + return fcFromJsonVarLenIntFc(jsonFcObj, type, std::move(attrs)); + } else if (type == jsonstr::nullTerminatedStr || type == jsonstr::staticLenStr || + type == jsonstr::dynLenStr) { + return fcFromJsonStrFc(jsonFcObj, type, std::move(attrs)); + } else if (type == jsonstr::staticLenBlob || type == jsonstr::dynLenBlob) { + return fcFromJsonBlobFc(jsonFcObj, type, std::move(attrs)); + } else if (type == jsonstr::staticLenArray || type == jsonstr::dynLenArray) { + return this->_fcFromJsonArrayFc(jsonFcObj, type, std::move(attrs)); + } else if (type == jsonstr::structure) { + return this->_fcFromJsonStructFc(jsonFcObj, std::move(attrs)); + } else if (type == jsonstr::optional) { + return this->_fcFromJsonOptionalFc(jsonFcObj, std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::variant); + return this->_fcFromJsonVariantFc(jsonFcObj, std::move(attrs)); + } +} + +void Ctf2FcBuilder::addFcAlias(std::string name, Fc::UP fc, const bt2c::TextLoc& nameLoc) +{ + /* Check for duplicate */ + if (bt2c::contains(_mFcAliases, name)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, nameLoc, + "Duplicate field class alias named `{}`.", name); + } + + /* Add to map */ + _mFcAliases.emplace(std::move(name), std::move(fc)); +} + +Fc::UP Ctf2FcBuilder::_aliasedFc(const std::string& name, const bt2c::TextLoc& loc) const +{ + /* Check if the field class alias exists */ + const auto it = _mFcAliases.find(name); + + if (it == _mFcAliases.end()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, loc, + "Cannot find field class alias `{}`.", name); + } + + /* Return a clone of the field class alias */ + return it->second->clone(); +} + +Fc::UP Ctf2FcBuilder::_fcFromJsonArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, + OptAttrs&& attrs) const +{ + /* Element field class */ + Fc::UP elemFc; + + try { + elemFc = this->buildFcFromJsonVal(*jsonFc[jsonstr::elemFc]); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid array field class."); + } + + /* Minimum alignment */ + const auto minAlign = minAlignOfJsonFc(jsonFc); + + /* Create field class */ + if (type == jsonstr::staticLenArray) { + return createStaticLenArrayFc(jsonFc.loc(), lenOfJsonFc(jsonFc), std::move(elemFc), + minAlign, false, std::move(attrs)); + } else { + BT_ASSERT(type == jsonstr::dynLenArray); + return createDynLenArrayFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc), + std::move(elemFc), minAlign, std::move(attrs)); + } +} + +Fc::UP Ctf2FcBuilder::_fcFromJsonStructFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const +{ + /* Minimum alignment */ + const auto minAlign = minAlignOfJsonFc(jsonFc); + + /* Member classes */ + StructFc::MemberClasses memberClasses; + const auto jsonMemberClasses = jsonFc[jsonstr::memberClasses]; + + try { + if (jsonMemberClasses) { + for (auto& jsonMemberCls : jsonMemberClasses->asArray()) { + auto& jsonMemberClsObj = jsonMemberCls->asObj(); + auto& name = jsonMemberClsObj.rawStrVal(jsonstr::name); + + try { + memberClasses.emplace_back(createStructFieldMemberCls( + name, this->buildFcFromJsonVal(*jsonMemberClsObj[jsonstr::fc]), + attrsOfObj(jsonMemberClsObj))); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + jsonMemberClsObj.loc(), "Invalid structure field member class `{}`.", name); + } + } + } + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), + "Invalid structure field class."); + } + + /* Create field class */ + return createStructFc(jsonFc.loc(), std::move(memberClasses), minAlign, std::move(attrs)); +} + +Fc::UP Ctf2FcBuilder::_fcFromJsonOptionalFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const +{ + try { + /* + * Create optional field class. + * + * The existence of the `selector-field-ranges` property + * determines the expected field class type: + * + * Property exists: + * Class of optional fields with an integer selector. + * + * Property doesn't exist: + * Class of optional fields with a boolean selector. + * + * Note that we just trust the metadata at this point so there's + * no validation: this can be done later with fcDepTypes(). + * + * Also, if the optional field class has instances having an + * integer selector, we can't know the signedness of such a + * selector at this point. Therefore we enforce an unsigned + * integer range set and will possibly cast to signed integer + * ranges later once we know the selector signedness. + */ + const auto jsonSelFieldRanges = jsonFc[jsonstr::selFieldRanges]; + auto fieldLoc = fieldLocOfJsonFc(jsonFc, jsonstr::selFieldLoc); + auto fc = this->buildFcFromJsonVal(*jsonFc[jsonstr::fc]); + + if (jsonSelFieldRanges) { + /* Expected integer selector */ + return createOptionalFc(jsonFc.loc(), std::move(fc), std::move(fieldLoc), + intRangeSetFromJsonIntRangeSet( + jsonSelFieldRanges->asArray()), + std::move(attrs)); + } else { + /* Expected boolean selector */ + return createOptionalFc(jsonFc.loc(), std::move(fc), std::move(fieldLoc), + std::move(attrs)); + } + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid optional field class."); + } +} + +Fc::UP Ctf2FcBuilder::_fcFromJsonVariantFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const +{ + try { + auto& jsonOpts = jsonFc[jsonstr::opts]->asArray(); + + /* + * We can't know the signedness of the selector at this point. + * Therefore we enforce an unsigned integer range set and will + * possibly cast to signed integer ranges later once we know the + * selector signedness. + */ + VariantWithUIntSelFc::Opts opts; + + for (auto it = jsonOpts.begin(); it != jsonOpts.end(); ++it) { + auto& jsonOpt = (*it)->asObj(); + + /* Create and append option */ + try { + opts.emplace_back( + createVariantFcOpt(this->buildFcFromJsonVal(*jsonOpt[jsonstr::fc]), + intRangeSetFromJsonIntRangeSet( + jsonOpt[jsonstr::selFieldRanges]->asArray()), + optStrOfObj(jsonOpt, jsonstr::name), attrsOfObj(jsonOpt))); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + jsonFc.loc(), "Invalid variant field class option #{}.", it - jsonOpts.begin()); + } + } + + /* Create field class */ + return createVariantFc(jsonFc.loc(), std::move(opts), + fieldLocOfJsonFc(jsonFc, jsonstr::selFieldLoc), std::move(attrs)); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid variant field class."); + } +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp new file mode 100644 index 00000000..aab4eceb --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP + +#include +#include + +#include "cpp-common/bt2c/json-val.hpp" +#include "cpp-common/bt2c/logging.hpp" +#include "cpp-common/bt2c/text-loc.hpp" + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* + * Field class builder for CTF 2. + * + * An instance keeps a map of field class alias names to field + * class aliases. + * + * Build a field class from an equivalent CTF 2 JSON value + * with buildFcFromJsonVal(). + * + * Add a field class alias with addFcAlias(). + */ +class Ctf2FcBuilder final +{ +public: + /* + * Builds a field class builder without any initial field + * class alias. + */ + explicit Ctf2FcBuilder(const bt2c::Logger& parentLogger); + + /* + * Builds and returns a field class from the CTF 2 JSON (string + * (alias) or object) value `jsonFc`, or appends a cause to the + * error of the current thread and throws `bt2c::Error` on error. + */ + Fc::UP buildFcFromJsonVal(const bt2c::JsonVal& jsonFc) const; + + /* + * Adds a field class alias `fc` named `name`, or appends a cause to + * the error of the current thread and throws `bt2c::Error` + * on error. + */ + void addFcAlias(std::string name, Fc::UP fc, const bt2c::TextLoc& nameLoc); + +private: + /* + * Returns a clone of the field class alias named `name`. + */ + Fc::UP _aliasedFc(const std::string& name, const bt2c::TextLoc& loc) const; + + /* + * Creates and returns an array field class from the JSON array + * field class value `jsonFc` and the other parameters. + */ + Fc::UP _fcFromJsonArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, + OptAttrs&& userAttrs) const; + + /* + * Creates and returns a structure field class from the JSON + * structure field class value `jsonFc` and from `userAttrs`. + */ + Fc::UP _fcFromJsonStructFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const; + + /* + * Creates and returns an optional field class from the JSON + * optional field class value `jsonFc` and from `userAttrs`. + */ + Fc::UP _fcFromJsonOptionalFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const; + + /* + * Creates and returns a variant field class from the JSON variant + * field class value `jsonFc` and from `userAttrs`. + */ + Fc::UP _fcFromJsonVariantFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const; + + /* Map of alias name to aliased field classes */ + std::unordered_map _mFcAliases; + + /* Logger */ + bt2c::Logger _mLogger; +}; + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp new file mode 100644 index 00000000..c1e6d8df --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp @@ -0,0 +1,820 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "common/assert.h" +#include "cpp-common/bt2c/contains.hpp" +#include "cpp-common/bt2c/exc.hpp" +#include "cpp-common/bt2c/join.hpp" +#include "cpp-common/bt2c/json-val.hpp" +#include "cpp-common/bt2c/logging.hpp" +#include "cpp-common/bt2c/make-span.hpp" +#include "cpp-common/bt2c/parse-json-as-val.hpp" +#include "cpp-common/bt2s/string-view.hpp" + +#include "ctf-2-metadata-stream-parser.hpp" +#include "fcs-with-role.hpp" +#include "normalize-field-locs.hpp" +#include "plugins/ctf/common/src/metadata/ctf-ir.hpp" +#include "resolve-fcs-with-int-sel.hpp" +#include "strings.hpp" +#include "utils.hpp" +#include "validate-scope-fc-roles.hpp" + +namespace ctf { +namespace src { + +Ctf2MetadataStreamParser::Ctf2MetadataStreamParser( + const bt2::OptionalBorrowedObject selfComp, const ClkClsCfg& clkClsCfg, + const bt2c::Logger& parentLogger) : + MetadataStreamParser {selfComp, clkClsCfg}, + _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-META-STREAM-PARSER"}, _mFragmentValReq {_mLogger}, + _mDefClkOffsetVal {bt2c::call([] { + bt2c::JsonObjVal::Container entries; + + entries.insert( + std::make_pair(jsonstr::seconds, bt2c::createJsonVal(0LL, bt2c::TextLoc {}))); + entries.insert( + std::make_pair(jsonstr::cycles, bt2c::createJsonVal(0ULL, bt2c::TextLoc {}))); + return bt2c::createJsonVal(std::move(entries), bt2c::TextLoc {}); + })}, + _mFcBuilder {_mLogger} +{ +} + +MetadataStreamParser::ParseRet +Ctf2MetadataStreamParser::parse(const bt2::OptionalBorrowedObject selfComp, + const ClkClsCfg& clkClsCfg, const bt2c::ConstBytes buffer, + const bt2c::Logger& parentLogger) +{ + Ctf2MetadataStreamParser parser {selfComp, clkClsCfg, parentLogger}; + + parser.parseSection(buffer); + + if (!parser.traceCls() || parser.traceCls()->dataStreamClasses().empty()) { + /* + * CTF 2 requires that a metadata stream contains at least one + * data stream class fragment: `parser.traceCls()`, if it + * exists, doesn't at this point and we know that `buffer` + * contains the whole metadata stream. + */ + BT_CPPLOGE_APPEND_CAUSE_AND_THROW_SPEC( + parser._mLogger, bt2::Error, "Missing data stream class fragment in metadata stream."); + } + + return {parser.releaseTraceCls(), parser.metadataStreamUuid()}; +} + +void Ctf2MetadataStreamParser::_parseSection(const bt2c::ConstBytes buffer) +{ + this->_parseFragments(buffer); +} + +void Ctf2MetadataStreamParser::_parseFragments(const bt2c::ConstBytes buffer) +{ + BT_ASSERT(buffer.data()); + + auto fragmentBegin = buffer.begin(); + const auto curSectionOffsetInStream = _mCurOffsetInStream; + + while (true) { + /* Find the beginning pointer of the current JSON fragment */ + while (fragmentBegin != buffer.end() && *fragmentBegin == 30) { + /* Skip RS byte */ + ++fragmentBegin; + } + + _mCurOffsetInStream = + curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - buffer.begin()); + + if (fragmentBegin == buffer.end()) { + /* We're done */ + return; + } + + /* Find the end pointer of the current JSON fragment */ + auto fragmentEnd = fragmentBegin; + + while (fragmentEnd != buffer.end() && *fragmentEnd != 30) { + /* Skip non-RS byte */ + ++fragmentEnd; + } + + if (fragmentBegin == fragmentEnd) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, this->_loc(buffer, fragmentBegin), "Expecting a fragment."); + } + + /* Parse one fragment */ + _mCurOffsetInStream = + curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - buffer.begin()); + this->_parseFragment(bt2c::makeSpan(fragmentBegin, fragmentEnd)); + + /* Go to next fragment */ + fragmentBegin = fragmentEnd; + ++_mCurFragmentIndex; + } + + /* Adjust offset in metadata stream for the next section to parse */ + _mCurOffsetInStream = curSectionOffsetInStream + bt2c::DataLen::fromBytes(buffer.size()); +} + +void Ctf2MetadataStreamParser::_parseFragment(const bt2c::ConstBytes buffer) +{ + try { + this->_handleFragment(*bt2c::parseJson( + bt2s::string_view {reinterpret_cast(buffer.data()), buffer.size()}, + _mCurOffsetInStream.bytes(), _mLogger)); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + this->_loc(buffer, buffer.begin()), "Invalid fragment #{}.", _mCurFragmentIndex + 1); + } +} + +namespace { + +/* + * Returns the UUID of the JSON object value `jsonObjVal`, or + * `bt2s::nullopt` if there's no such property. + */ +bt2s::optional uuidOfObj(const bt2c::JsonObjVal& jsonObjVal) +{ + if (const auto jsonUuidVal = jsonObjVal[jsonstr::uuid]) { + std::array uuid; + auto& jsonArrayUuidVal = jsonUuidVal->asArray(); + + std::transform(jsonArrayUuidVal.begin(), jsonArrayUuidVal.end(), uuid.begin(), + [](const bt2c::JsonVal::UP& jsonUuidElemVal) { + return static_cast(*jsonUuidElemVal->asUInt()); + }); + return bt2c::Uuid {uuid.data()}; + } + + return bt2s::nullopt; +} + +} /* namespace */ + +void Ctf2MetadataStreamParser::_handleFragment(const bt2c::JsonVal& jsonFragment) +{ + /* Validate the fragment */ + _mFragmentValReq.validate(jsonFragment); + + /* Get type */ + auto& jsonFragmentObj = jsonFragment.asObj(); + auto& type = jsonFragmentObj.rawStrVal(jsonstr::type); + + /* Specific preamble fragment case */ + if (_mCurFragmentIndex == 0) { + if (type != jsonstr::preamble) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, jsonFragmentObj.loc(), + "Expecting the preamble fragment."); + } + + /* Possibly set the metadata stream UUID */ + _mMetadataStreamUuid = uuidOfObj(jsonFragmentObj); + + /* + * Nothing more to do with the preamble fragment, but it + * must exist! + */ + return; + } + + /* Defer to specific method */ + if (type == jsonstr::preamble) { + BT_ASSERT(_mCurFragmentIndex > 0); + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, jsonFragmentObj.loc(), + "Preamble fragment must be the first fragment of the metadata stream."); + } else if (type == jsonstr::fcAlias) { + this->_handleFcAliasFragment(jsonFragmentObj); + } else if (type == jsonstr::traceCls) { + this->_handleTraceClsFragment(jsonFragmentObj); + } else if (type == jsonstr::clkCls) { + this->_handleClkClsFragment(jsonFragmentObj); + } else if (type == jsonstr::dataStreamCls) { + this->_handleDataStreamClsFragment(jsonFragmentObj); + } else { + BT_ASSERT(type == jsonstr::eventRecordCls); + this->_handleEventRecordClsFragment(jsonFragmentObj); + } +} + +void Ctf2MetadataStreamParser::_handleFcAliasFragment(const bt2c::JsonObjVal& jsonFragment) +{ + auto& jsonNameVal = jsonFragment[jsonstr::name]->asStr(); + + try { + _mFcBuilder.addFcAlias(*jsonNameVal, + _mFcBuilder.buildFcFromJsonVal(*jsonFragment[jsonstr::fc]), + jsonNameVal.loc()); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFragment.loc(), + "Invalid field class alias fragment."); + } +} + +void Ctf2MetadataStreamParser::_validateScopeFcRoles(const Fc& fc, + const UIntFieldRoles& allowedRoles, + const bool allowMetadataStreamUuidRole) +{ + validateScopeFcRoles(fc, allowedRoles, allowMetadataStreamUuidRole, _mLogger); +} + +void Ctf2MetadataStreamParser::_validatePktHeaderFcRoles(const Fc& pktHeaderFc) +{ + /* Validate allowed roles */ + this->_validateScopeFcRoles(pktHeaderFc, + {UIntFieldRole::PktMagicNumber, UIntFieldRole::DataStreamClsId, + UIntFieldRole::DataStreamId}, + true); + + /* + * Validate that, if an unsigned integer field class FC has a + * "packet magic number" role: + * + * • FC is a 32-bit fixed-length unsigned integer field class. + * + * • FC is the field class of the first member class of the packet + * header scope structure field class. + * + * • There's only one such field class within the whole packet + * header scope field class. + */ + { + const auto fcs = fcsWithRole(pktHeaderFc, {UIntFieldRole::PktMagicNumber}, false); + + if (fcs.size() > 1) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, (*fcs.begin())->loc(), + "Packet header field class may contain zero or one field class having the role `{}`, not {}.", + jsonstr::pktMagicNumber, fcs.size()); + } + + if (!fcs.empty()) { + auto& magicFc = **fcs.begin(); + + if (&pktHeaderFc.asStruct()[0].fc() != &magicFc) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, magicFc.loc(), + "A field class having the `{}` role must be the first class of the first member class " + "of the packet header field class.", + jsonstr::pktMagicNumber); + } + + if (!magicFc.isFixedLenUInt()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, magicFc.loc(), + "Unexpected type of field class having the `{}` role: " + "expecting `{}`.", + jsonstr::pktMagicNumber, jsonstr::fixedLenUInt); + } + + auto& magicUIntFc = magicFc.asFixedLenUInt(); + + if (*magicUIntFc.len() != 32) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, magicFc.loc(), + "Unexpected `{}` property of fixed-length unsigned integer field class having the `{}` role: " + "expecting 32, not {}.", + jsonstr::len, jsonstr::pktMagicNumber, *magicUIntFc.len()); + } + } + } + + /* + * Validate that, if there's at least one static-length BLOB field + * class having the "metadata stream UUID" role, then the metadata + * stream has a UUID. + */ + { + const auto fcs = fcsWithRole(pktHeaderFc, {}, true); + + if (!fcs.empty() && !_mMetadataStreamUuid) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, (*fcs.begin())->loc(), + "Static-length BLOB field class has the role `{}`, " + "but the preamble fragment of the metadata stream has no `{}` property.", + jsonstr::metadataStreamUuid, jsonstr::uuid); + } + } +} + +namespace { + +/* + * A namespace, name, and UID tuple. + */ +struct NsNameUid final +{ + bt2s::optional ns; + bt2s::optional name; + bt2s::optional uid; +}; + +/* + * Returns the namespace, name, and UID of the JSON object `jsonObj`. + */ +NsNameUid nsNameUidOfObj(const bt2c::JsonObjVal& jsonObj) +{ + return NsNameUid { + optStrOfObj(jsonObj, jsonstr::ns), + optStrOfObj(jsonObj, jsonstr::name), + optStrOfObj(jsonObj, jsonstr::uid), + }; +} + +} /* namespace */ + +void Ctf2MetadataStreamParser::_handleTraceClsFragment(const bt2c::JsonObjVal& jsonFragment) +{ + /* Check for trace class uniqueness */ + if (_mTraceCls) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, jsonFragment.loc(), + "Duplicate trace class fragment."); + } + + /* Namespace, name, and UID */ + auto nsNameUid = nsNameUidOfObj(jsonFragment); + + /* Create the packet header field class and validate it */ + auto pktHeaderFc = + this->_scopeFcOfJsonVal(jsonFragment, jsonstr::pktHeaderFc, Scope::PktHeader); + + if (pktHeaderFc) { + try { + this->_validatePktHeaderFcRoles(*pktHeaderFc); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(pktHeaderFc->loc(), + "Invalid packet header field class."); + } + } + + /* Create the trace class */ + _mTraceCls = createTraceCls(std::move(nsNameUid.ns), std::move(nsNameUid.name), + std::move(nsNameUid.uid), bt2ValueOfObj(jsonFragment, jsonstr::env), + std::move(pktHeaderFc), attrsOfObj(jsonFragment)); +} + +void Ctf2MetadataStreamParser::_handleClkClsFragment(const bt2c::JsonObjVal& jsonFragment) +{ + /* ID */ + auto id = jsonFragment.rawStrVal(jsonstr::id); + + if (bt2c::contains(_mClkClasses, id)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, jsonFragment.loc(), "Duplicate clock class fragment with ID `{}`.", id); + } + + /* Namespace, name, and UID */ + auto nsNameUid = nsNameUidOfObj(jsonFragment); + + /* Create corresponding clock class */ + auto clkCls = createClkCls( + id, jsonFragment.rawUIntVal(jsonstr::freq), std::move(nsNameUid.ns), + std::move(nsNameUid.name), std::move(nsNameUid.uid), bt2c::call([this, &jsonFragment] { + auto& jsonOffsetVal = jsonFragment.val(jsonstr::offsetFromOrigin, *_mDefClkOffsetVal); + + return ClkOffset { + bt2c::call([&jsonOffsetVal] { + if (const auto jsonOffsetSecsVal = jsonOffsetVal[jsonstr::seconds]) { + return rawIntValFromJsonIntVal(*jsonOffsetSecsVal); + } + + return 0LL; + }), + jsonOffsetVal.rawVal(jsonstr::cycles, 0ULL), + }; + }), + bt2c::call([&jsonFragment]() -> bt2s::optional { + if (const auto jsonOriginVal = jsonFragment[jsonstr::origin]) { + if (jsonOriginVal->isStr()) { + /* Unix epoch */ + BT_ASSERT(*jsonOriginVal->asStr() == jsonstr::unixEpoch); + return ClkOrigin {}; + } else { + /* Custom */ + auto& jsonOriginObjVal = jsonOriginVal->asObj(); + + /* Create clock origin */ + auto nsNameUid = nsNameUidOfObj(jsonOriginObjVal); + + BT_ASSERT(nsNameUid.name); + BT_ASSERT(nsNameUid.uid); + return ClkOrigin {std::move(nsNameUid.ns), std::move(*nsNameUid.name), + std::move(*nsNameUid.uid)}; + } + } + + return bt2s::nullopt; + }), + optStrOfObj(jsonFragment, jsonstr::descr), + bt2c::call([&jsonFragment]() -> bt2s::optional { + if (const auto jsonPrecision = jsonFragment[jsonstr::precision]) { + return *jsonPrecision->asUInt(); + } + + return bt2s::nullopt; + }), + bt2c::call([&jsonFragment]() -> bt2s::optional { + if (const auto jsonAccuracy = jsonFragment[jsonstr::accuracy]) { + return *jsonAccuracy->asUInt(); + } + + return bt2s::nullopt; + }), + attrsOfObj(jsonFragment)); + + /* Add to map of clock classes */ + _mClkClasses.emplace(std::make_pair(std::move(id), std::move(clkCls))); +} + +namespace { + +/* + * Returns the "full class ID" string of `id`, `ns`, `name`, and `uid`. + */ +std::string fullClsIdStr(const unsigned long long id, const bt2s::optional& ns, + const bt2s::optional& name, + const bt2s::optional& uid) +{ + std::ostringstream ss; + + ss << id; + + if (ns || name || uid) { + std::vector parts; + + if (ns) { + parts.push_back(*ns); + } + + if (name) { + parts.push_back(*name); + } + + if (uid) { + parts.push_back(*uid); + } + + ss << fmt::format(" ({})", bt2c::join(parts, "/")); + } + + return ss.str(); +} + +/* + * Returns the "full class ID" string of `id` and `nsNameUid`. + */ +std::string fullClsIdStr(const unsigned long long id, const NsNameUid& nsNameUid) +{ + return fullClsIdStr(id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid); +} + +/* + * Returns the "full class ID" string of the class object `cls`. + */ +template +std::string fullClsIdStr(const ClsT& cls) +{ + return fullClsIdStr(cls.id(), cls.ns(), cls.name(), cls.uid()); +} + +} /* namespace */ + +void Ctf2MetadataStreamParser::_validateClkTsRoles(const Fc& fc, const bool allowClkTsRole) +{ + const auto fcs = + fcsWithRole(fc, {UIntFieldRole::DefClkTs, UIntFieldRole::PktEndDefClkTs}, false); + + if (!fcs.empty() && !allowClkTsRole) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, (*fcs.begin())->loc(), + "Invalid unsigned integer field class having the `{}` or `{}` role because " + "its containing data stream class fragment has no default clock class " + "(missing `{}` property).", + jsonstr::defClkTs, jsonstr::pktEndDefClkTs, jsonstr::defClkClsId); + } +} + +void Ctf2MetadataStreamParser::_validateDataStreamClsRoles(const Fc * const pktCtxFc, + const Fc * const eventRecordHeaderFc, + const Fc * const commonEventRecordCtxFc, + const bool allowClkTsRole) +{ + if (pktCtxFc) { + try { + this->_validateScopeFcRoles(*pktCtxFc, + {UIntFieldRole::PktTotalLen, UIntFieldRole::PktContentLen, + UIntFieldRole::DefClkTs, UIntFieldRole::PktEndDefClkTs, + UIntFieldRole::DiscEventRecordCounterSnap, + UIntFieldRole::PktSeqNum}, + false); + this->_validateClkTsRoles(*pktCtxFc, allowClkTsRole); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(pktCtxFc->loc(), + "Invalid packet context field class."); + } + } + + if (eventRecordHeaderFc) { + try { + this->_validateScopeFcRoles(*eventRecordHeaderFc, + {UIntFieldRole::DefClkTs, UIntFieldRole::EventRecordClsId}, + false); + this->_validateClkTsRoles(*eventRecordHeaderFc, allowClkTsRole); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + eventRecordHeaderFc->loc(), "Invalid event record header field class."); + } + } + + if (commonEventRecordCtxFc) { + try { + /* No roles allowed */ + this->_validateScopeFcRoles(*commonEventRecordCtxFc, {}, false); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + commonEventRecordCtxFc->loc(), "Invalid common event record context field class."); + } + } +} + +namespace { + +/* + * A string and text location pair. + */ +struct StrAndLoc final +{ + std::string str; + bt2c::TextLoc loc; +}; + +/* + * Returns the string value and text location of the property `propName` + * within `jsonObjVal`, or `bt2s::nullopt` if there's no such property. + */ +bt2s::optional optStrOfObjWithLoc(const bt2c::JsonObjVal& jsonObjVal, + const std::string& propName) +{ + if (const auto jsonVal = jsonObjVal[propName]) { + return StrAndLoc {*jsonVal->asStr(), jsonVal->loc()}; + } + + return bt2s::nullopt; +} + +} /* namespace */ + +void Ctf2MetadataStreamParser::_handleDataStreamClsFragment(const bt2c::JsonObjVal& jsonFragment) +{ + this->_ensureExistingTraceCls(); + + /* ID */ + const auto id = jsonFragment.rawVal(jsonstr::id, 0ULL); + + if ((*_mTraceCls)[id]) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, jsonFragment.loc(), + "Duplicate data stream class fragment with numeric ID {}.", id); + } + + /* Default clock class */ + auto defClkCls = bt2c::call([this, &jsonFragment]() -> ClkCls::SP { + if (const auto defClkClsId = optStrOfObjWithLoc(jsonFragment, jsonstr::defClkClsId)) { + const auto it = _mClkClasses.find(defClkClsId->str); + + if (it == _mClkClasses.end()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, defClkClsId->loc, + "`{}` doesn't identify an existing clock class fragment.", defClkClsId->str); + } + + return it->second; + } + + return {}; + }); + + /* Namespace, name, and UID */ + const auto nsNameUid = nsNameUidOfObj(jsonFragment); + + /* Create data stream class */ + try { + /* Create field classes */ + auto pktCtxFc = + this->_dataStreamClsScopeFcOfJsonVal(jsonFragment, jsonstr::pktCtxFc, Scope::PktCtx); + auto eventRecordHeaderFc = this->_dataStreamClsScopeFcOfJsonVal( + jsonFragment, jsonstr::eventRecordHeaderFc, Scope::EventRecordHeader, pktCtxFc.get()); + auto commonEventRecordCtxFc = this->_dataStreamClsScopeFcOfJsonVal( + jsonFragment, jsonstr::eventRecordCommonCtxFc, Scope::CommonEventRecordCtx, + pktCtxFc.get(), eventRecordHeaderFc.get()); + + /* Validate roles */ + this->_validateDataStreamClsRoles(pktCtxFc.get(), eventRecordHeaderFc.get(), + commonEventRecordCtxFc.get(), + static_cast(defClkCls)); + + /* Create and add data stream class to current trace class */ + _mTraceCls->addDataStreamCls(createDataStreamCls( + id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid, std::move(pktCtxFc), + std::move(eventRecordHeaderFc), std::move(commonEventRecordCtxFc), std::move(defClkCls), + attrsOfObj(jsonFragment))); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFragment.loc(), + "Invalid data stream class fragment {}.", + fullClsIdStr(id, nsNameUid)); + } +} + +void Ctf2MetadataStreamParser::_validateEventRecordClsRoles(const Fc * const specCtxFc, + const Fc * const payloadFc) +{ + if (specCtxFc) { + try { + /* No roles allowed */ + this->_validateScopeFcRoles(*specCtxFc, {}, false); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(specCtxFc->loc(), + "Invalid specific context field class."); + } + } + + if (payloadFc) { + try { + /* No roles allowed */ + this->_validateScopeFcRoles(*payloadFc, {}, false); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(payloadFc->loc(), + "Invalid payload field class."); + } + } +} + +void Ctf2MetadataStreamParser::_handleEventRecordClsFragment(const bt2c::JsonObjVal& jsonFragment) +{ + this->_ensureExistingTraceCls(); + + /* Data stream class ID */ + auto dataStreamCls = bt2c::call([this, &jsonFragment] { + const auto jsonDataStreamClsIdVal = jsonFragment[jsonstr::dataStreamClsId]; + const auto dataStreamClsId = + jsonDataStreamClsIdVal ? *jsonDataStreamClsIdVal->asUInt() : 0ULL; + + if (auto dataStreamCls = (*_mTraceCls)[dataStreamClsId]) { + return dataStreamCls; + } + + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, + jsonDataStreamClsIdVal ? jsonDataStreamClsIdVal->loc() : jsonFragment.loc(), + "No data stream class fragment exists with numeric ID {}.", dataStreamClsId); + }); + + /* ID */ + const auto id = bt2c::call([this, &jsonFragment, &dataStreamCls] { + const auto jsonIdVal = jsonFragment[jsonstr::id]; + const auto id = jsonIdVal ? *jsonIdVal->asUInt() : 0ULL; + + if ((*dataStreamCls)[id]) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, jsonIdVal ? jsonIdVal->loc() : jsonFragment.loc(), + "Duplicate event record class fragment with numeric ID {} within data stream class fragment {}.", + id, fullClsIdStr(*dataStreamCls)); + } + + return id; + }); + + /* Create event record class */ + const auto nsNameUid = nsNameUidOfObj(jsonFragment); + + try { + /* Create field classes */ + auto specCtxFc = this->_eventRecordClsScopeFcOfJsonVal( + jsonFragment, jsonstr::specCtxFc, Scope::SpecEventRecordCtx, *dataStreamCls); + auto payloadFc = this->_eventRecordClsScopeFcOfJsonVal(jsonFragment, jsonstr::payloadFc, + Scope::EventRecordPayload, + *dataStreamCls, specCtxFc.get()); + + /* Validate roles */ + this->_validateEventRecordClsRoles(specCtxFc.get(), payloadFc.get()); + + /* Create and add event record class to data stream class */ + dataStreamCls->addEventRecordCls(createEventRecordCls( + id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid, std::move(specCtxFc), + std::move(payloadFc), attrsOfObj(jsonFragment))); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + jsonFragment.loc(), + "Invalid event record class fragment {} (for data stream class fragment {}).", + fullClsIdStr(id, nsNameUid), fullClsIdStr(*dataStreamCls)); + } +} + +void Ctf2MetadataStreamParser::_ensureExistingTraceCls() +{ + if (_mTraceCls) { + /* Already initialized */ + return; + } + + /* Create a default CTF 2 trace class */ + _mTraceCls = createTraceCls(); +} + +Fc::UP Ctf2MetadataStreamParser::_scopeFcOfJsonVal(const bt2c::JsonObjVal& jsonVal, + const std::string& key, const Scope scope, + const Fc *pktHeaderFc, const Fc *pktCtxFc, + const Fc *eventRecordHeaderFc, + const Fc *commonEventRecordCtxFc, + const Fc *specEventRecordCtxFc, + const Fc *eventRecordPayloadFc) +{ + const auto jsonFcVal = jsonVal[key]; + + if (!jsonFcVal) { + /* Scope doesn't exist */ + return nullptr; + } + + try { + /* Build field class */ + auto fc = _mFcBuilder.buildFcFromJsonVal(*jsonFcVal); + + /* Make sure it's a structure field class */ + if (!fc->isStruct()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, fc->loc(), + "Expecting a structure field class."); + } + + /* Normalize field locations (relative → absolute) */ + normalizeFieldLocs(*fc, scope, _mLogger); + + /* + * Resolve `OptionalWithUIntSel` and `VariantWithUIntSel` + * field classes. + */ + switch (scope) { + case Scope::PktHeader: + pktHeaderFc = fc.get(); + break; + case Scope::PktCtx: + pktCtxFc = fc.get(); + break; + case Scope::EventRecordHeader: + eventRecordHeaderFc = fc.get(); + break; + case Scope::CommonEventRecordCtx: + commonEventRecordCtxFc = fc.get(); + break; + case Scope::SpecEventRecordCtx: + specEventRecordCtxFc = fc.get(); + break; + case Scope::EventRecordPayload: + eventRecordPayloadFc = fc.get(); + break; + default: + bt_common_abort(); + } + + resolveFcsWithIntSel(*fc, scope, pktHeaderFc, pktCtxFc, eventRecordHeaderFc, + commonEventRecordCtxFc, specEventRecordCtxFc, eventRecordPayloadFc, + _mLogger); + + /* Done! */ + return fc; + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonVal.loc(), "Invalid {} scope.", + scopeStr(scope)); + } +} + +Fc::UP Ctf2MetadataStreamParser::_eventRecordClsScopeFcOfJsonVal( + const bt2c::JsonObjVal& jsonEventRecordCls, const std::string& key, const Scope scope, + const DataStreamCls& dataStreamCls, const Fc * const specEventRecordCtxFc, + const Fc * const eventRecordPayloadFc) +{ + return this->_scopeFcOfJsonVal(jsonEventRecordCls, key, scope, _mTraceCls->pktHeaderFc(), + dataStreamCls.pktCtxFc(), dataStreamCls.eventRecordHeaderFc(), + dataStreamCls.commonEventRecordCtxFc(), specEventRecordCtxFc, + eventRecordPayloadFc); +} + +Fc::UP Ctf2MetadataStreamParser::_dataStreamClsScopeFcOfJsonVal( + const bt2c::JsonObjVal& jsonDataStreamCls, const std::string& key, const Scope scope, + const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc, + const Fc * const commonEventRecordCtxFc) +{ + return this->_scopeFcOfJsonVal(jsonDataStreamCls, key, scope, _mTraceCls->pktHeaderFc(), + pktCtxFc, eventRecordHeaderFc, commonEventRecordCtxFc); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp new file mode 100644 index 00000000..841d9ec6 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP + +#include + +#include + +#include "../ctf-ir.hpp" +#include "../metadata-stream-parser.hpp" +#include "ctf-2-fc-builder.hpp" +#include "val-req.hpp" + +namespace ctf { +namespace src { + +/* + * CTF 2 metadata stream (JSON text sequence) parser. + * + * Build an instance of `Ctf2MetadataStreamParser`, and then call + * parseSection() as often as needed with one or more _complete_ + * CTF 2 fragments. + * + * You may also call the static Ctf2MetadataStreamParser::parse() method + * to parse a whole CTF 2 metadata stream. + */ +class Ctf2MetadataStreamParser final : public MetadataStreamParser +{ +public: + /* + * Builds a CTF 2 metadata stream parser. + * + * If `selfComp` exists, then the parser uses it each time you call + * parseSection() to finalize the current trace class. + */ + explicit Ctf2MetadataStreamParser(bt2::OptionalBorrowedObject selfComp, + const ClkClsCfg& clkClsCfg, const bt2c::Logger& parentLogger); + + /* + * Parses the whole CTF 2 metadata stream in `buffer` and returns + * the resulting trace class and optional metadata stream UUID on + * success, or appends a cause to the error of the current thread + * and throws `bt2c::Error` otherwise. + */ + static ParseRet parse(bt2::OptionalBorrowedObject selfComp, + const ClkClsCfg& clkClsCfg, bt2c::ConstBytes buffer, + const bt2c::Logger& parentLogger); + +private: + void _parseSection(bt2c::ConstBytes buffer) override; + + /* + * Parses one or more complete fragments in `buffer`, updating the + * internal state on success, or appending a cause to the error of + * the current thread and throwing `bt2c::Error` otherwise. + */ + void _parseFragments(bt2c::ConstBytes buffer); + + /* + * Parses the JSON fragment in `buffer`, updating the internal state + * on success, or appending a cause to the error of the current + * thread and throwing `bt2c::Error` on failure. + */ + void _parseFragment(bt2c::ConstBytes buffer); + + /* + * Handles the JSON fragment `jsonFragment`, updating the internal + * state on success, or appending a cause to the error of the + * current thread and throwing `bt2c::Error` on failure. + */ + void _handleFragment(const bt2c::JsonVal& jsonFragment); + + /* + * Handles the JSON field class alias fragment `jsonFragment`, + * updating the internal state on success, or appending a cause to + * the error of the current thread and throwing `bt2c::Error` + * on failure. + */ + void _handleFcAliasFragment(const bt2c::JsonObjVal& jsonFragment); + + /* + * Forwards to validateScopeFcRoles() using the logger of + * this parser.. + * + * Appends a cause to the error of the current thread and throwing + * `bt2c::Error` on failure. + */ + void _validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles, + bool allowMetadataStreamUuidRole); + + /* + * Validates the field roles of the packet header field class `fc`. + */ + void _validatePktHeaderFcRoles(const Fc& fc); + + /* + * Validates the field roles of the JSON trace class fragment + * `jsonFragment`. + */ + void _validateTraceClsFragmentRoles(const bt2c::JsonObjVal& jsonFragment); + + /* + * Handles the JSON trace class fragment `jsonFragment`, updating + * the internal state on success, or appending a cause to the error + * of the current thread and throwing `bt2c::Error` on + * failure. + */ + void _handleTraceClsFragment(const bt2c::JsonObjVal& jsonFragment); + + /* + * Handles the JSON clock class fragment `jsonFragment`, updating + * the internal state on success, or appending a cause to the error + * of the current thread and throwing `bt2c::Error` on + * failure. + */ + void _handleClkClsFragment(const bt2c::JsonObjVal& jsonFragment); + + /* + * Validates that the field class `fc` doesn't contain an unsigned + * integer field class having a default clock timestamp role if + * `allowDefClkTsRole` is false. + */ + void _validateClkTsRoles(const Fc& fc, bool allowClkTsRole); + + /* + * Validates the roles of the packet context, event record header, + * and common event record context field classes `pktCtxFc`, + * `eventRecordHeaderFc`, and `commonEventRecordCtxFc`. + */ + void _validateDataStreamClsRoles(const Fc *pktCtxFc, const Fc *eventRecordHeaderFc, + const Fc *commonEventRecordCtxFc, bool allowDefClkTsRole); + + /* + * Handles the JSON data stream class fragment `jsonFragment`, + * updating the internal state on success, or appending a cause to + * the error of the current thread and throwing `bt2c::Error` + * on failure. + */ + void _handleDataStreamClsFragment(const bt2c::JsonObjVal& jsonFragment); + + /* + * Validates the roles of specific event record context and event + * record payload field classes `specCtxFc` and `payloadFc`. + */ + void _validateEventRecordClsRoles(const Fc *specCtxFc, const Fc *payloadFc); + + /* + * Handles the JSON event record class fragment `jsonFragment`, + * updating the internal state on success, or appending a cause to + * the error of the current thread and throwing `bt2c::Error` + * on failure. + */ + void _handleEventRecordClsFragment(const bt2c::JsonObjVal& jsonFragment); + + /* + * Ensures that `*_mTraceCls` exists. + */ + void _ensureExistingTraceCls(); + + /* + * If a JSON value has the key `key` in `jsonVal`: + * Returns `nullptr`. + * + * Otherwise: + * Converts `*jsonVal[key]` to a scope field class and returns + * it, considering `pktHeaderFc`, `pktCtxFc`, + * `eventRecordHeaderFc`, `commonEventRecordCtxFc`, + * `specEventRecordCtxFc`, and `eventRecordPayloadFc` as the + * current packet header, packet context, event record header, + * common event record context, specific event record context, + * and event record payload field classes. + */ + Fc::UP _scopeFcOfJsonVal(const bt2c::JsonObjVal& jsonVal, const std::string& key, Scope scope, + const Fc *pktHeaderFc = nullptr, const Fc *pktCtxFc = nullptr, + const Fc *eventRecordHeaderFc = nullptr, + const Fc *commonEventRecordCtxFc = nullptr, + const Fc *specEventRecordCtxFc = nullptr, + const Fc *eventRecordPayloadFc = nullptr); + + /* + * If a JSON value has the key `key` in the JSON event record class + * fragment value `jsonVal`: + * Returns `nullptr`. + * + * Otherwise: + * Converts `*jsonEventRecordCls[key]` to a scope field class + * and returns it, considering the current packet header field + * class, the field classes of `dataStreamCls`, and + * `specEventRecordCtxFc` and `eventRecordPayloadFc` as the + * current specific event record context and event record + * payload field classes. + */ + Fc::UP _eventRecordClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonEventRecordCls, + const std::string& key, Scope scope, + const DataStreamCls& dataStreamCls, + const Fc *specEventRecordCtxFc = nullptr, + const Fc *eventRecordPayloadFc = nullptr); + + /* + * If a JSON value has the key `key` in the JSON data stream class + * fragment value `jsonDataStreamCls`: + * Returns `nullptr`. + * + * Otherwise: + * Converts `*jsonDataStreamCls[key]` to a field class and + * returns it, considering the current packet header field + * class, and `pktCtxFc`, `eventRecordHeaderFc`, and + * `commonEventRecordCtxFc` as the current packet context, event + * record header, and common event record context field classes. + */ + Fc::UP _dataStreamClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonDataStreamCls, + const std::string& key, Scope scope, + const Fc *pktCtxFc = nullptr, + const Fc *eventRecordHeaderFc = nullptr, + const Fc *commonEventRecordCtxFc = nullptr); + + /* + * If a JSON value has the key `key` in the JSON trace class + * fragment value `jsonTraceCls`: + * Returns `nullptr`. + * + * Otherwise: + * Converts `*jsonTraceCls[key]` to a field class and returns + * it, considering the JSON trace class fragment value + * `jsonTraceCls` as the conversion context. + */ + Fc::UP _traceClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonTraceCls, const std::string& key); + + /* + * Returns a text location with an offset of `at` relative to the + * `buffer.begin()`, also considering `_mCurOffsetInStream`. + * + * `at` must be within `buffer`. + */ + bt2c::TextLoc _loc(bt2c::ConstBytes buffer, + const bt2c::ConstBytes::const_iterator at) const noexcept + { + BT_ASSERT_DBG(at >= buffer.begin()); + BT_ASSERT_DBG(at < buffer.end()); + return bt2c::TextLoc {_mCurOffsetInStream.bytes() + (at - buffer.begin())}; + } + +private: + /* Logging configuration */ + bt2c::Logger _mLogger; + + /* Current offset within the whole metadata stream */ + bt2c::DataLen _mCurOffsetInStream = bt2c::DataLen::fromBytes(0); + + /* Current fragment index */ + std::size_t _mCurFragmentIndex = 0; + + /* Fragment requirement */ + Ctf2JsonAnyFragmentValReq _mFragmentValReq; + + /* Default clock offset JSON value */ + bt2c::JsonObjVal::UP _mDefClkOffsetVal; + + /* + * Map of clock class ID to clock class object. + * + * Clock class fragments "float" in a CTF 2 metadata stream, in that + * they aren't used yet, but could be afterwards through an ID + * reference within a data stream class. + * + * This map stores them until the parser needs one for a data + * stream class. + */ + std::unordered_map _mClkClasses; + + /* Field class builder, which keeps track of field class aliases */ + Ctf2FcBuilder _mFcBuilder; +}; + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp new file mode 100644 index 00000000..5fef6fea --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include "cpp-common/bt2c/contains.hpp" + +#include "fcs-with-role.hpp" + +namespace ctf { +namespace src { +namespace { + +/* + * Helper of fcsWithRole(). + */ +class Finder final : public ConstFcVisitor +{ +public: + explicit Finder(const UIntFieldRoles& roles, const bool withMetadataStreamUuidRole) : + _mRoles {&roles}, _mWithMetadataStreamUuidRole {withMetadataStreamUuidRole} + { + } + + std::unordered_set takeFcs() noexcept + { + return std::move(_mFcs); + } + +private: + void visit(const FixedLenUIntFc& fc) override + { + this->_tryAddUIntFc(fc); + } + + void visit(const VarLenUIntFc& fc) override + { + this->_tryAddUIntFc(fc); + } + + void visit(const StaticLenBlobFc& fc) override + { + if (_mWithMetadataStreamUuidRole && fc.hasMetadataStreamUuidRole()) { + _mFcs.emplace(&fc); + } + } + + void visit(const StaticLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(const DynLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(const StructFc& fc) override + { + for (auto& memberCls : fc) { + memberCls.fc().accept(*this); + } + } + + void visit(const OptionalWithBoolSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithUIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithSIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const VariantWithUIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + void visit(const VariantWithSIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + template + void _tryAddUIntFc(const UIntFcT& fc) + { + for (const auto role : fc.roles()) { + if (bt2c::contains(*_mRoles, role)) { + _mFcs.emplace(&fc); + } + } + } + + void _visit(const ArrayFc& fc) + { + fc.elemFc().accept(*this); + } + + void _visit(const OptionalFc& fc) + { + fc.fc().accept(*this); + } + + template + void _visitVariantFc(const VarFcT& fc) + { + for (auto& opt : fc) { + opt.fc().accept(*this); + } + } + + const UIntFieldRoles *_mRoles; + bool _mWithMetadataStreamUuidRole; + std::unordered_set _mFcs; +}; + +} /* namespace */ + +std::unordered_set fcsWithRole(const Fc& fc, const UIntFieldRoles& roles, + const bool withMetadataStreamUuidRole) +{ + Finder finder {roles, withMetadataStreamUuidRole}; + + fc.accept(finder); + return finder.takeFcs(); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp new file mode 100644 index 00000000..690d1523 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP + +#include + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* + * Returns a set of all the field classes having at least one role + * amongst `roles` and/or the "metadata stream UUID" role if + * `withMetadataStreamUuidRole` is true for a static-length BLOB + * field class. + */ +std::unordered_set fcsWithRole(const Fc& fc, const UIntFieldRoles& roles, + bool withMetadataStreamUuidRole); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp b/src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp new file mode 100644 index 00000000..79df873d --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +#include "common/assert.h" +#include "common/common.h" +#include "cpp-common/bt2c/contains.hpp" +#include "cpp-common/bt2c/logging.hpp" +#include "cpp-common/bt2c/text-loc-str.hpp" + +#include "key-fc-types.hpp" +#include "strings.hpp" +#include "utils.hpp" + +namespace ctf { +namespace src { +namespace { + +/* + * Helper of keyFcTypes(). + */ +class KeyFcTypesFinder final : public ConstFcVisitor +{ +private: + /* Current scope field classes */ + struct _Ctx final + { + const Fc *pktHeaderFc; + const Fc *pktCtxFc; + const Fc *eventRecordHeaderFc; + const Fc *commonEventRecordCtxFc; + const Fc *specEventRecordCtxFc; + const Fc *eventRecordPayloadFc; + }; + + /* Set of const field classes */ + using _ConstFcSet = std::unordered_set; + +public: + explicit KeyFcTypesFinder(const Scope scope, const Fc * const pktHeaderFc, + const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc, + const Fc * const commonEventRecordCtxFc, + const Fc * const specEventRecordCtxFc, + const Fc * const eventRecordPayloadFc, + const bt2c::Logger& parentLogger) : + _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-FC-DEP-TYPES"}, + _mScope {scope}, _mCtx {pktHeaderFc, pktCtxFc, + eventRecordHeaderFc, commonEventRecordCtxFc, + specEventRecordCtxFc, eventRecordPayloadFc} + { + } + + /* + * Moves the resulting field class dependency type map to + * the caller. + */ + KeyFcTypes takeKeyFcTypes() noexcept + { + return std::move(_mKeyFcTypes); + } + +private: + void visit(const StaticLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(const DynLenArrayFc& fc) override + { + this->_addDynLenKeyFcType(fc); + this->_visit(fc); + } + + void visit(const DynLenStrFc& fc) override + { + this->_addDynLenKeyFcType(fc); + } + + void visit(const DynLenBlobFc& fc) override + { + this->_addDynLenKeyFcType(fc); + } + + void visit(const StructFc& fc) override + { + for (auto& memberCls : fc) { + try { + memberCls.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + memberCls.fc().loc(), "Invalid structure field member class."); + } + } + } + + void visit(const OptionalWithBoolSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithUIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithSIntSelFc&) override + { + /* Doesn't exist at this point */ + bt_common_abort(); + } + + void visit(const VariantWithUIntSelFc& fc) override + { + this->_addOptionalOrVariantKeyFcType(fc); + + for (auto optIt = fc.begin(); optIt != fc.end(); ++optIt) { + try { + this->_withinCompoundFc(fc, optIt - fc.begin(), [this, &optIt] { + optIt->fc().accept(*this); + }); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(optIt->fc().loc(), + "Invalid variant field class option."); + } + } + } + + void visit(const VariantWithSIntSelFc&) override + { + /* Doesn't exist at this point */ + bt_common_abort(); + } + + void _visit(const ArrayFc& fc) + { + try { + this->_withinCompoundFc(fc, [this, &fc] { + fc.elemFc().accept(*this); + }); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid element field class of array field class."); + } + } + + void _visit(const OptionalFc& fc) + { + const auto keyFcType = this->_addOptionalOrVariantKeyFcType(fc); + + if (fc.isOptionalWithBoolSel() && keyFcType != KeyFcType::Bool) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fc.loc(), + "Expecting a class of optional fields with a boolean selector field " + "because the `{}` property is absent.", + jsonstr::selFieldRanges); + } + + try { + this->_withinCompoundFc(fc, [this, &fc] { + fc.fc().accept(*this); + }); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid field class of optional field class."); + } + } + + /* + * Adds the key field class type of the dynamic-length field class + * `fc` to `_mKeyFcTypes`. + */ + template + void _addDynLenKeyFcType(FcT& fc) + { + this->_validateDynLenKeyFcType(this->_addKeyFcs(fc, fc.lenFieldLoc()), fc.lenFieldLoc()); + } + + /* + * Adds the key field class type of the optional or variant field + * class `fc` to `_mKeyFcTypes` and returns said type. + */ + template + KeyFcType _addOptionalOrVariantKeyFcType(FcT& fc) + { + return this->_keyFcType(**this->_addKeyFcs(fc, fc.selFieldLoc()).begin()); + } + + /* + * Adds the key field class type of `fc` to `_mKeyFcTypes` and + * returns the dependencies of `fc`. + */ + template + _ConstFcSet _addKeyFcs(FcT& fc, const FieldLoc& fieldLoc) + { + auto keyFcs = this->_findKeyFcs(fc, fieldLoc); + + _mKeyFcTypes.emplace(std::make_pair(&fc, this->_keyFcType(**keyFcs.begin()))); + return keyFcs; + } + + /* + * Adds to `keyFcs` the key field classes of `dependentFc`, using + * the field location `fieldLoc`, from `baseFc` and the field + * location item iterator `fieldLocIt`. + * + * Returns `true` if `dependentFc` isn't reached yet (safe to + * continue to find key field classes). + */ + bool _findKeyFcs(const Fc& baseFc, const Fc& dependentFc, const FieldLoc& fieldLoc, + const FieldLoc::Items::const_iterator fieldLocIt, _ConstFcSet& keyFcs) const + { + if (baseFc.isFixedLenBool() || baseFc.isInt()) { + if (fieldLocIt != fieldLoc.end()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), + "Cannot reach anything beyond a scalar field class for {}.", + absFieldLocStr(fieldLoc, fieldLocIt + 1)); + } + + keyFcs.insert(&baseFc); + return true; + } else if (baseFc.isStruct()) { + if (fieldLocIt == fieldLoc.end()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), + "Field location must not locate a structure field class."); + } + + /* Find the member class named `**fieldLocIt` */ + for (auto& memberCls : baseFc.asStruct()) { + if (&memberCls.fc() == &dependentFc) { + /* Reached the dependent field class */ + return false; + } + + if (memberCls.name() != **fieldLocIt) { + continue; + } + + return this->_findKeyFcs(memberCls.fc(), dependentFc, fieldLoc, fieldLocIt + 1, + keyFcs); + } + + /* Member class not found */ + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), + "At field location {}: no structure field member class named `{}`.", + absFieldLocStr(fieldLoc, fieldLocIt), **fieldLocIt); + } else if (baseFc.isArray()) { + if (!bt2c::contains(_mCompoundFcIndexes, &baseFc)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), + "At field location {}: unreachable array field element.", + absFieldLocStr(fieldLoc, fieldLocIt)); + } + + auto& elemFc = baseFc.asArray().elemFc(); + + if (&elemFc == &dependentFc) { + /* Reached the dependent field class */ + return false; + } + + return this->_findKeyFcs(elemFc, dependentFc, fieldLoc, fieldLocIt, keyFcs); + } else if (baseFc.isOptional()) { + if (!bt2c::contains(_mCompoundFcIndexes, &baseFc)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), "At field location {}: unreachable optional field.", + absFieldLocStr(fieldLoc, fieldLocIt)); + } + + auto& optionalFc = baseFc.asOptional().fc(); + + if (&optionalFc == &dependentFc) { + /* Reached the dependent field class */ + return false; + } + + return this->_findKeyFcs(optionalFc, dependentFc, fieldLoc, fieldLocIt, keyFcs); + } else if (baseFc.isVariant()) { + auto& opts = baseFc.asVariantWithUIntSel().opts(); + const auto curOptIndexIt = _mCompoundFcIndexes.find(&baseFc); + + if (curOptIndexIt == _mCompoundFcIndexes.end()) { + /* + * Not currently visiting this variant field class: + * consider all options. + */ + for (auto& opt : opts) { + if (&opt.fc() == &dependentFc) { + /* Reached the dependent field class */ + return false; + } + + if (!this->_findKeyFcs(opt.fc(), dependentFc, fieldLoc, fieldLocIt, keyFcs)) { + /* Reached the dependent field class */ + return false; + } + } + } else { + /* + * Currently visiting this variant field class: consider + * only the currently visited option. + */ + auto& optFc = opts[curOptIndexIt->second].fc(); + + if (&optFc == &dependentFc) { + /* Reached the dependent field class */ + return false; + } + + return this->_findKeyFcs(optFc, dependentFc, fieldLoc, fieldLocIt, keyFcs); + } + + return true; + } else { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, baseFc.loc(), "At field location {}: unexpected type of field class.", + absFieldLocStr(fieldLoc, fieldLocIt)); + } + } + + /* + * Returns the dependency type from the type of `fc`. + */ + static KeyFcType _keyFcType(const Fc& fc) noexcept + { + if (fc.isFixedLenBool()) { + return KeyFcType::Bool; + } else if (fc.isUInt()) { + return KeyFcType::UInt; + } else { + BT_ASSERT(fc.isSInt()); + return KeyFcType::SInt; + } + }; + + /* + * Returns the field class (within `_mCtx`) of the scope + * of `fieldLoc`. + */ + const Fc& _scopeFc(const FieldLoc& fieldLoc) const + { + /* Validate the scope first */ + if (static_cast(*fieldLoc.origin()) > static_cast(_mScope)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fieldLoc.loc(), + "A field within a {} field cannot depend on another field " + "within a {} field (unreachable).", + scopeStr(_mScope), scopeStr(*fieldLoc.origin())); + } + + /* Retrieve scope field class from `_mCtx` */ + const auto scopeFc = bt2c::call([this, &fieldLoc] { + switch (*fieldLoc.origin()) { + case Scope::PktHeader: + return _mCtx.pktHeaderFc; + case Scope::PktCtx: + return _mCtx.pktCtxFc; + case Scope::EventRecordHeader: + return _mCtx.eventRecordHeaderFc; + case Scope::CommonEventRecordCtx: + return _mCtx.commonEventRecordCtxFc; + case Scope::SpecEventRecordCtx: + return _mCtx.specEventRecordCtxFc; + case Scope::EventRecordPayload: + return _mCtx.eventRecordPayloadFc; + default: + bt_common_abort(); + } + }); + + if (!scopeFc) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, fieldLoc.loc(), + "Missing required {} field class.", + scopeStr(*fieldLoc.origin())); + } + + return *scopeFc; + } + + /* + * Finds the key field classes of `dependentFc` using the field + * location `fieldLoc`. + * + * This method only considers boolean and integer field classes as + * key field classes, throwing `bt2c::Error` when it finds + * anything else. + * + * This method doesn't add to the returned set field classes which + * occur after `dependentFc` . + * + * This method also throws if: + * + * • `fieldLoc` is invalid anyhow. + * + * • `fieldLoc` locates field classes having different key field + * class types. + * + * • `fieldLoc` doesn't locate any field class. + */ + _ConstFcSet _findKeyFcs(const Fc& dependentFc, const FieldLoc& fieldLoc) const + { + try { + /* Find key field classes and return them */ + _ConstFcSet keyFcs; + + this->_findKeyFcs(this->_scopeFc(fieldLoc), dependentFc, fieldLoc, fieldLoc.begin(), + keyFcs); + + /* Validate that `keyFcs` contains at least one item */ + if (keyFcs.empty()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fieldLoc.loc(), "Field location doesn't locate anything."); + } + + /* + * Validate that all the items of `keyFcs` have the + * same type. + */ + { + const auto expectedType = this->_keyFcType(**keyFcs.begin()); + + for (const auto fc : keyFcs) { + if (this->_keyFcType(*fc) != expectedType) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fieldLoc.loc(), + "Field location locates field classes having different types " + "([{}] and [{}]).", + bt2c::textLocStr((*keyFcs.begin())->loc(), _mLogger.textLocStrFmt()), + bt2c::textLocStr(fc->loc(), _mLogger.textLocStrFmt())); + } + } + } + + /* Return the set */ + return keyFcs; + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(fieldLoc.loc(), + "Invalid field location {}.", + absFieldLocStr(fieldLoc, fieldLoc.end())); + } + } + + /* + * Validates the key field class type `keyFcType` for some + * dynamic-length field class. + */ + void _validateDynLenKeyFcType(const _ConstFcSet& keyFcs, const FieldLoc& fieldLoc) const + { + BT_ASSERT(!keyFcs.empty()); + + if (this->_keyFcType(**keyFcs.begin()) != KeyFcType::UInt) { + try { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, (*keyFcs.begin())->loc(), + "Expecting an unsigned integer field class."); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fieldLoc.loc(), "Invalid field location {}.", + absFieldLocStr(fieldLoc, fieldLoc.end())); + } + } + } + + /* + * In this order: + * + * 1. Marks the underlying field class at the index `index` of the + * compound field class `fc` as being currently visited. + * + * 2. Calls `func()`. + * + * 3. Cancels 1. + */ + template + void _withinCompoundFc(const Fc& fc, const std::size_t index, FuncT&& func) + { + BT_ASSERT(!bt2c::contains(_mCompoundFcIndexes, &fc)); + _mCompoundFcIndexes.emplace(std::make_pair(&fc, index)); + func(); + _mCompoundFcIndexes.erase(&fc); + } + + /* + * In this order: + * + * 1. Marks the compound field class `fc` as being + * currently visited. + * + * 2. Calls `func()`. + * + * 3. Cancels 1. + */ + template + void _withinCompoundFc(const Fc& fc, FuncT&& func) + { + this->_withinCompoundFc(fc, 0, std::forward(func)); + } + + bt2c::Logger _mLogger; + Scope _mScope; + _Ctx _mCtx; + + /* Result */ + KeyFcTypes _mKeyFcTypes; + + /* + * Map of compound field classes to the index of the currently + * visited immediate underlying field class, that is: + * + * For a variant field class F: + * Index of the option of F containing the field class currently + * being visited. + * + * For an array field class F: + * For an optional field class F: + * 0: if F is part of the map, then its element/optional field + * class is currently being visited. + * + * This is used to provide a visiting context to _findKeyFcs() so as + * to follow the correct variant field class option as well as to + * validate key field classes. + * + * For example: + * + * Root: Structure FC [0] + * `len`: Fixed-length unsigned integer FC [1] + * `meow`: Dynamic-length array FC [2] + * Element FC: Structure FC [3] + * `tag`: Fixed-length signed integer FC [4] + * `val`: Variant FC [5] + * `boss`: Null-terminated string FC [6] + * `zoom`: Structure FC [7] + * `len`: Variable-length unsigned integer FC [8] + * `data`: Dynamic-length BLOB FC [9] + * `line6`: Structure FC [10] + * `len`: Fixed-length unsigned integer FC [11] + * + * If _findKeyFcs() is currently visiting [9] to find its + * dependencies, then the map would contain: + * + * [2] → 0 (visiting current element of `/meow`) + * [5] → 1 (visiting second option (`zoom`) of `/meow/val`) + * + * This means that, if the length field location of [9] is + * `/meow/val/len`, then we must only consider the `zoom` option, + * not the `line6` one, even though both contain a member class + * named `len`. + */ + std::unordered_map _mCompoundFcIndexes; +}; + +} /* namespace */ + +KeyFcTypes keyFcTypes(const Fc& scopeFc, const Scope scope, const Fc * const pktHeaderFc, + const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc, + const Fc * const commonEventRecordCtxFc, + const Fc * const specEventRecordCtxFc, const Fc * const eventRecordPayloadFc, + const bt2c::Logger& parentLogger) +{ + KeyFcTypesFinder finder {scope, + pktHeaderFc, + pktCtxFc, + eventRecordHeaderFc, + commonEventRecordCtxFc, + specEventRecordCtxFc, + eventRecordPayloadFc, + parentLogger}; + + scopeFc.accept(finder); + return finder.takeKeyFcTypes(); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp b/src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp new file mode 100644 index 00000000..8b3dadb3 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP + +#include + +#include "cpp-common/bt2c/logging.hpp" +#include "cpp-common/vendor/wise-enum/wise_enum.h" + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* clang-format off */ + +/* Key (length/selector) field class type */ +WISE_ENUM_CLASS(KeyFcType, + Bool, + UInt, + SInt +) + +/* clang-format on */ + +/* Map of dependent field class to key field class type */ +using KeyFcTypes = std::unordered_map; + +/* + * Returns the (validated) key types of all the dependent field classes + * within `scopeFc`. + * + * `scopeFc` is a scope field class for the scope `scope`. + * + * `pktHeaderFc`, `pktCtxFc`, `eventRecordHeaderFc`, + * `commonEventRecordCtxFc`, `specEventRecordCtxFc`, and + * `eventRecordPayloadFc` are the current packet header, packet context, + * event record header, common event record context, specific event + * record context, and event record payload field classes. Any of them + * may be `nullptr`. + * + * The field locations within `scopeFc` must be absolute. + * + * Appends one or more causes to the error of the current thread and + * throws `bt2c::Error` when any field location in `scopeFc` is invalid. + */ +KeyFcTypes keyFcTypes(const Fc& scopeFc, Scope scope, const Fc *pktHeaderFc, const Fc *pktCtxFc, + const Fc *eventRecordHeaderFc, const Fc *commonEventRecordCtxFc, + const Fc *specEventRecordCtxFc, const Fc *eventRecordPayloadFc, + const bt2c::Logger& parentLogger); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp new file mode 100644 index 00000000..e65028e7 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include "common/assert.h" +#include "common/common.h" +#include "cpp-common/bt2c/logging.hpp" + +#include "normalize-field-locs.hpp" + +namespace ctf { +namespace src { +namespace { + +/* + * Helper of normalizeFieldLocs(). + */ +class Normalizer final : public FcVisitor +{ +public: + explicit Normalizer(const Scope scope, const bt2c::Logger& parentLogger) : + _mScope {scope}, _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-NORMALIZE-FIELD-LOCS"} + { + } + +private: + void visit(StaticLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(DynLenArrayFc& fc) override + { + fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc())); + this->_visit(fc); + } + + void visit(DynLenStrFc& fc) override + { + fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc())); + } + + void visit(DynLenBlobFc& fc) override + { + fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc())); + } + + void visit(StructFc& fc) override + { + for (auto& memberCls : fc) { + _mMemberNames.push_back(&memberCls.name()); + + try { + memberCls.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + memberCls.fc().loc(), "Invalid structure field member class."); + } + + _mMemberNames.pop_back(); + } + } + + void visit(OptionalWithBoolSelFc& fc) override + { + this->_visit(fc); + } + + void visit(OptionalWithUIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(OptionalWithSIntSelFc&) override + { + /* Doesn't exist at this point */ + bt_common_abort(); + } + + void visit(VariantWithUIntSelFc& fc) override + { + fc.selFieldLoc(this->_normalizeFieldLoc(fc.selFieldLoc())); + + for (auto& opt : fc) { + try { + opt.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(), + "Invalid variant field class option."); + } + } + } + + void visit(VariantWithSIntSelFc&) override + { + /* Doesn't exist at this point */ + bt_common_abort(); + } + + void _visit(ArrayFc& fc) + { + try { + fc.elemFc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid element field class of array field class."); + } + } + + void _visit(OptionalFc& fc) + { + fc.selFieldLoc(this->_normalizeFieldLoc(fc.selFieldLoc())); + + try { + fc.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid field class of optional field class."); + } + } + + /* + * Returns the normalized version of `fieldLoc`. + */ + FieldLoc _normalizeFieldLoc(const FieldLoc& fieldLoc) const + { + if (fieldLoc.origin()) { + /* Already absolute */ + return fieldLoc; + } + + /* + * First remove non-leading "parent" field location items. + * + * For example (using a file system notation): + * + * • `../../meow/mix/../glue/all` → `../../meow/glue/all`. + * • `hello/../../world` → `../world`. + */ + const auto tmpItems = bt2c::call([&fieldLoc] { + FieldLoc::Items retItems; + + for (auto& item : fieldLoc.items()) { + if (retItems.empty()) { + retItems.push_back(item); + } else { + if (item) { + retItems.push_back(item); + } else { + /* Go back to known parent */ + retItems.pop_back(); + } + } + } + + return retItems; + }); + + /* + * Create absolute field location items. + * + * Example 1: with leading "parent" items + * ────────────────────────────────────── + * Given the temporary items `../meow/mix` and the current + * member names `/red/blue/green` (`green` being the name of the + * dependent field class having the location `fieldLoc`), then + * after the loop below: + * + * /red/blue/green + * ▲ + * memberNameEndIt + * + * ../meow/mix + * ▲ + * tmpItemIt + * + * Final, absolute items: + * + * /red/meow/mix + * + * Example 2: without leading "parent" items + * ───────────────────────────────────────── + * Given the temporary items `meow/mix` and the current member + * names `/red/blue/green` (`green` being the name of the + * dependent field class having the location `fieldLoc`), then + * after the loop below: + * + * /red/blue/green + * ▲ + * memberNameEndIt + * + * meow/mix + * ▲ + * tmpItemIt + * + * Final, absolute items: + * + * /red/blue/meow/mix + */ + BT_ASSERT(!_mMemberNames.empty()); + + auto tmpItemIt = tmpItems.begin(); + auto memberNameEndIt = _mMemberNames.end() - 1; + + for (; tmpItemIt != tmpItems.end(); ++tmpItemIt) { + if (*tmpItemIt) { + /* End of leading "parent" items */ + break; + } + + if (memberNameEndIt == _mMemberNames.begin()) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fieldLoc.loc(), + "Invalid field location: too many \"parent\" path items."); + } + + --memberNameEndIt; + } + + FieldLoc::Items items; + + for (auto memberNameIt = _mMemberNames.begin(); memberNameIt != memberNameEndIt; + ++memberNameIt) { + items.push_back(**memberNameIt); + } + + for (; tmpItemIt != tmpItems.end(); ++tmpItemIt) { + items.push_back(*tmpItemIt); + } + + /* Create absolute field location */ + return createFieldLoc(fieldLoc.loc(), _mScope, std::move(items)); + } + + std::vector _mMemberNames; + Scope _mScope; + bt2c::Logger _mLogger; +}; + +} /* namespace */ + +void normalizeFieldLocs(Fc& scopeFc, const Scope scope, const bt2c::Logger& parentLogger) +{ + Normalizer normalizer {scope, parentLogger}; + + scopeFc.accept(normalizer); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp new file mode 100644 index 00000000..1df9f386 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP + +#include "cpp-common/bt2c/logging.hpp" + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* + * Converts all the relative field locations in `scopeFc` into absolute + * field locations. + * + * `scopeFc` is a scope field class for the scope `scope`. + * + * ┌────────────────────────────────────────────────────────────────┐ + * │ IMPORTANT: This function doesn't guarantee that the normalized │ + * │ field locations are valid. │ + * └────────────────────────────────────────────────────────────────┘ + * + * Appends one or more causes to the error of the current thread using + * and throws `bt2c::Error` when any relative field location in + * `scopeFc` is invalid. + */ +void normalizeFieldLocs(Fc& scopeFc, Scope scope, const bt2c::Logger& parentLogger); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp new file mode 100644 index 00000000..2d09452a --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +#include "common/assert.h" +#include "cpp-common/bt2c/logging.hpp" + +#include "key-fc-types.hpp" +#include "resolve-fcs-with-int-sel.hpp" + +namespace ctf { +namespace src { +namespace { + +SIntRangeSet sIntRangeSetFromUIntRangeSet(const UIntRangeSet& uIntRanges) +{ + SIntRangeSet::Set sIntRanges; + + for (auto& uIntRange : uIntRanges) { + sIntRanges.emplace(SIntRangeSet::Range {static_cast(uIntRange.lower()), + static_cast(uIntRange.upper())}); + } + + return SIntRangeSet {std::move(sIntRanges)}; +} + +/* + * Helper of resolveFcsWithIntSel(). + */ +class Resolver final : public FcVisitor +{ +public: + explicit Resolver(const KeyFcTypes& keyFcTypes, const bt2c::Logger& parentLogger) : + _mKeyFcTypes {&keyFcTypes}, _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-RES-FCS-WITH-INT-SEL"} + { + } + +private: + void visit(StaticLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(DynLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(StructFc& fc) override + { + for (auto& memberCls : fc) { + try { + memberCls.fc(this->_resolveFc(memberCls.takeFc())); + memberCls.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + memberCls.fc().loc(), "Invalid structure field member class."); + } + } + } + + void visit(OptionalWithBoolSelFc& fc) override + { + this->_visit(fc); + } + + void visit(OptionalWithUIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(OptionalWithSIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(VariantWithUIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + void visit(VariantWithSIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + void _visit(ArrayFc& fc) + { + try { + fc.elemFc(this->_resolveFc(fc.takeElemFc())); + fc.elemFc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid element field class of array field class."); + } + } + + void _visit(OptionalFc& fc) + { + try { + fc.fc(this->_resolveFc(fc.takeFc())); + fc.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid field class of optional field class."); + } + } + + template + void _visitVariantFc(VarFcT& fc) + { + for (auto& opt : fc) { + try { + opt.fc(this->_resolveFc(opt.takeFc())); + opt.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(), + "Invalid variant field class option."); + } + } + } + + Fc::UP _resolveFc(Fc::UP fc) + { + if (!fc->isOptionalWithUIntSel() && !fc->isVariantWithUIntSel()) { + /* Not a candidate for replacement: return as is */ + return fc; + } + + const auto keyFcType = _mKeyFcTypes->at(fc.get()); + + if (keyFcType == KeyFcType::UInt) { + /* Type of `*fc` is already correct: return as is */ + return fc; + } + + BT_ASSERT(keyFcType == KeyFcType::SInt); + + /* + * The goal here is to create an `OptionalWithSIntSel` or a + * `VariantWithSIntSel` field class from an + * `OptionalWithUIntSel` or a `VariantWithUIntSel` field class, + * stealing (moving) as much as possible from `*fc` as it will + * be discarded anyway (explaining all the take*() + * method calls). + * + * sIntRangeSetFromUIntRangeSet() converts the selector field + * ranges from unsigned to signed. + */ + if (fc->isOptionalWithUIntSel()) { + auto& optionalFc = fc->asOptionalWithUIntSel(); + + return createOptionalFc( + optionalFc.loc(), optionalFc.takeFc(), optionalFc.takeSelFieldLoc(), + sIntRangeSetFromUIntRangeSet(optionalFc.selFieldRanges()), optionalFc.takeAttrs()); + } else { + BT_ASSERT(fc->isVariantWithUIntSel()); + + auto& varFc = fc->asVariantWithUIntSel(); + VariantWithSIntSelFc::Opts newOpts; + + for (auto& opt : varFc) { + newOpts.emplace_back(createVariantFcOpt( + opt.takeFc(), sIntRangeSetFromUIntRangeSet(opt.selFieldRanges()), + opt.takeName(), opt.takeAttrs())); + } + + return createVariantFc(varFc.loc(), std::move(newOpts), varFc.takeSelFieldLoc(), + varFc.takeAttrs()); + } + } + + const KeyFcTypes *_mKeyFcTypes; + bt2c::Logger _mLogger; +}; + +} /* namespace */ + +void resolveFcsWithIntSel(Fc& scopeFc, const Scope scope, const Fc * const pktHeaderFc, + const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc, + const Fc * const commonEventRecordCtxFc, + const Fc * const specEventRecordCtxFc, + const Fc * const eventRecordPayloadFc, const bt2c::Logger& logger) +{ + const auto theKeyFcTypes = + keyFcTypes(scopeFc, scope, pktHeaderFc, pktCtxFc, eventRecordHeaderFc, + commonEventRecordCtxFc, specEventRecordCtxFc, eventRecordPayloadFc, logger); + Resolver resolver {theKeyFcTypes, logger}; + + scopeFc.accept(resolver); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp new file mode 100644 index 00000000..280919ca --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP + +#include "cpp-common/bt2c/logging.hpp" + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* + * Replaces all the field classes within `scopeFc` having the type + * `FcType::OptionalWithUIntSel` or `FcType::VariantWithUIntSel` with + * the correct versions, keeping their children as is. + * + * `scopeFc` is a scope field class for the scope `scope`. + * + * Also validates the dynamic-length field classes. + * + * This function uses keyFcTypes() behind the scenes, therefore all the + * preconditions of the latter apply. + * + * Appends one or more causes to the error of the current thread and + * throws `bt2c::Error` when any field class in `scopeFc` is invalid. + */ +void resolveFcsWithIntSel(Fc& scopeFc, Scope scope, const Fc *pktHeaderFc, const Fc *pktCtxFc, + const Fc *eventRecordHeaderFc, const Fc *commonEventRecordCtxFc, + const Fc *specEventRecordCtxFc, const Fc *eventRecordPayloadFc, + const bt2c::Logger& logger); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/utils.cpp b/src/plugins/ctf/common/src/metadata/json/utils.cpp new file mode 100644 index 00000000..d6010b16 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/utils.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "cpp-common/bt2c/bt2-value-from-json-val.hpp" + +#include "utils.hpp" + +namespace ctf { +namespace src { + +bt2::MapValue::Shared bt2ValueOfObj(const bt2c::JsonObjVal& jsonObjVal, const std::string& key) +{ + if (const auto jsonUserAttrsVal = jsonObjVal[key]) { + return bt2c::bt2ValueFromJsonVal(*jsonUserAttrsVal)->asMap().shared(); + } + + return bt2::MapValue::Shared {}; +} + +std::string absFieldLocStr(const FieldLoc& fieldLoc, const FieldLoc::Items::const_iterator end) +{ + std::ostringstream ss; + + BT_ASSERT(fieldLoc.origin()); + ss << '[' << scopeStr(*fieldLoc.origin()); + + for (auto it = fieldLoc.begin(); it != end; ++it) { + BT_ASSERT(*it); + ss << fmt::format(", `{}`", **it); + } + + ss << ']'; + return ss.str(); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/utils.hpp b/src/plugins/ctf/common/src/metadata/json/utils.hpp new file mode 100644 index 00000000..e783ed42 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/utils.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP + +#include + +#include "common/common.h" +#include "cpp-common/bt2/value.hpp" +#include "cpp-common/bt2c/json-val.hpp" + +#include "../ctf-ir.hpp" +#include "strings.hpp" + +namespace ctf { +namespace src { + +/* + * Returns the object of the JSON object value `jsonObjVal` having the + * key `key` as a libbabeltrace2 value object, or `bt2s::nullopt` if + * there's no such key. + */ +bt2::MapValue::Shared bt2ValueOfObj(const bt2c::JsonObjVal& jsonObjVal, const std::string& key); + +/* + * Returns the attributes of the JSON object value `jsonObjVal`, or + * `bt2s::nullopt` if there's no such property. + */ +inline bt2::MapValue::Shared attrsOfObj(const bt2c::JsonObjVal& jsonObjVal) +{ + return bt2ValueOfObj(jsonObjVal, jsonstr::attrs); +} + +/* + * Returns the raw integer value from the JSON unsigned or signed + * integer value `jsonIntVal`, casted as `ValT`. + */ +template +ValT rawIntValFromJsonIntVal(const bt2c::JsonVal& jsonIntVal) noexcept +{ + if (jsonIntVal.isUInt()) { + return static_cast(*jsonIntVal.asUInt()); + } else { + return static_cast(*jsonIntVal.asSInt()); + } +} + +/* + * Returns the optional raw string value from the property named + * `propName` within `jsonObjVal`. + */ +inline bt2s::optional optStrOfObj(const bt2c::JsonObjVal& jsonObjVal, + const char * const propName) +{ + const auto jsonVal = jsonObjVal[propName]; + + if (jsonVal) { + return *jsonVal->asStr(); + } + + return bt2s::nullopt; +} + +inline const char *scopeStr(const Scope scope) noexcept +{ + switch (scope) { + case Scope::PktHeader: + return "packet header"; + case Scope::PktCtx: + return "packet context"; + case Scope::EventRecordHeader: + return "event record header"; + case Scope::CommonEventRecordCtx: + return "common event record context"; + case Scope::SpecEventRecordCtx: + return "specific event record context"; + case Scope::EventRecordPayload: + return "event record payload"; + default: + bt_common_abort(); + } +} + +/* + * Returns a string representation of `fieldLoc`, considering all its + * path items until `end` (excluded). + * + * `fieldLoc` must be an absolute field location. + */ +std::string absFieldLocStr(const FieldLoc& fieldLoc, FieldLoc::Items::const_iterator end); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP */ diff --git a/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp new file mode 100644 index 00000000..0102eb1b --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2022-2023 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#include "cpp-common/bt2c/contains.hpp" + +#include "strings.hpp" +#include "validate-scope-fc-roles.hpp" + +namespace ctf { +namespace src { +namespace { + +const char *validScopeNamesForRole(const UIntFieldRole role) noexcept +{ + switch (role) { + case UIntFieldRole::PktMagicNumber: + case UIntFieldRole::DataStreamClsId: + case UIntFieldRole::DataStreamId: + return "a packet header"; + case UIntFieldRole::DefClkTs: + return "a packet context or an event record header"; + case UIntFieldRole::PktTotalLen: + case UIntFieldRole::PktContentLen: + case UIntFieldRole::PktEndDefClkTs: + case UIntFieldRole::DiscEventRecordCounterSnap: + case UIntFieldRole::PktSeqNum: + return "a packet context"; + case UIntFieldRole::EventRecordClsId: + return "an event record header"; + default: + bt_common_abort(); + } +} + +const char *uIntFcRoleJsonStr(const UIntFieldRole role) noexcept +{ + switch (role) { + case UIntFieldRole::PktMagicNumber: + return jsonstr::pktMagicNumber; + case UIntFieldRole::DataStreamClsId: + return jsonstr::dataStreamClsId; + case UIntFieldRole::DataStreamId: + return jsonstr::dataStreamId; + case UIntFieldRole::PktTotalLen: + return jsonstr::pktTotalLen; + case UIntFieldRole::PktContentLen: + return jsonstr::pktContentLen; + case UIntFieldRole::DefClkTs: + return jsonstr::defClkTs; + case UIntFieldRole::PktEndDefClkTs: + return jsonstr::pktEndDefClkTs; + case UIntFieldRole::DiscEventRecordCounterSnap: + return jsonstr::discEventRecordCounterSnap; + case UIntFieldRole::PktSeqNum: + return jsonstr::pktSeqNum; + case UIntFieldRole::EventRecordClsId: + return jsonstr::eventRecordClsId; + default: + bt_common_abort(); + } +} + +/* + * Helper of validateScopeFcRoles(). + */ +class Validator final : public ConstFcVisitor +{ +public: + explicit Validator(const UIntFieldRoles& allowedRoles, const bool allowMetadataStreamUuidRole, + const bt2c::Logger& parentLogger) : + _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-VALIDATE-SCOPE-FC-ROLES"}, + _mAllowedRoles {&allowedRoles}, _mAllowMetadataStreamUuidRole {allowMetadataStreamUuidRole} + { + } + +private: + void visit(const FixedLenUIntFc& fc) override + { + this->_validateUIntFc(fc); + } + + void visit(const VarLenUIntFc& fc) override + { + this->_validateUIntFc(fc); + } + + void visit(const StaticLenBlobFc& fc) override + { + if (fc.asStaticLenBlob().hasMetadataStreamUuidRole() && !_mAllowMetadataStreamUuidRole) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fc.loc(), + "Static-length BLOB field class may not have the `{}` role here " + "(only valid within a packet header field class).", + jsonstr::metadataStreamUuid); + } + } + + void visit(const StaticLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(const DynLenArrayFc& fc) override + { + this->_visit(fc); + } + + void visit(const StructFc& fc) override + { + for (auto& memberCls : fc) { + try { + memberCls.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + memberCls.fc().loc(), "Invalid structure field member class."); + } + } + } + + void visit(const OptionalWithBoolSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithUIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const OptionalWithSIntSelFc& fc) override + { + this->_visit(fc); + } + + void visit(const VariantWithUIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + void visit(const VariantWithSIntSelFc& fc) override + { + this->_visitVariantFc(fc); + } + + template + void _validateUIntFc(const UIntFcT& fc) const + { + for (const auto role : fc.roles()) { + if (!bt2c::contains(*_mAllowedRoles, role)) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW( + bt2c::Error, fc.loc(), + "Unsigned integer field class may not have the `{}` role here " + "(only valid within {} field class).", + uIntFcRoleJsonStr(role), validScopeNamesForRole(role)); + } + } + } + + void _visit(const ArrayFc& fc) + { + try { + fc.elemFc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid element field class of array field class."); + } + } + + void _visit(const OptionalFc& fc) + { + try { + fc.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW( + fc.loc(), "Invalid field class of optional field class."); + } + } + + template + void _visitVariantFc(const VarFcT& fc) + { + for (auto& opt : fc) { + try { + opt.fc().accept(*this); + } catch (const bt2c::Error&) { + BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(), + "Invalid variant field class option."); + } + } + } + + bt2c::Logger _mLogger; + const UIntFieldRoles *_mAllowedRoles; + bool _mAllowMetadataStreamUuidRole; +}; + +} /* namespace */ + +void validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles, + const bool allowMetadataStreamUuidRole, const bt2c::Logger& parentLogger) +{ + Validator validator {allowedRoles, allowMetadataStreamUuidRole, parentLogger}; + + fc.accept(validator); +} + +} /* namespace src */ +} /* namespace ctf */ diff --git a/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp new file mode 100644 index 00000000..b3510fc8 --- /dev/null +++ b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022-2024 Philippe Proulx + * + * SPDX-License-Identifier: MIT + */ + +#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP +#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP + +#include "cpp-common/bt2c/logging.hpp" + +#include "../ctf-ir.hpp" + +namespace ctf { +namespace src { + +/* + * Validates that: + * + * • If any unsigned integer field class within `fc` has one or more + * roles, then those roles are part of the `allowedRoles` set. + * + * • If any static-length BLOB field class within `fc` has the metadata + * stream UUID role, then `allowMetadataStreamUuidRole` is true. + * + * Appends one or more causes to the error of the current thread and + * throws `bt2c::Error` when `fc` is invalid. + */ +void validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles, + bool allowMetadataStreamUuidRole, const bt2c::Logger& parentLogger); + +} /* namespace src */ +} /* namespace ctf */ + +#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP */