sessiond: ust: conditionally enable the underscore prefix variant quirk
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 28 Nov 2022 23:02:44 +0000 (18:02 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 12 Jan 2023 21:22:12 +0000 (16:22 -0500)
Application contexts are expressed as variants. LTTng-UST announces
those by registering an enumeration named `..._tag`. It then registers a
variant as part of the event context that contains the various possible
types.

Unfortunately, the names used in the enumeration and variant don't
match: the enumeration names are all prefixed with an underscore while
the variant type tag fields aren't.

While the CTF 1.8.3 specification mentions that
underscores *should* (not *must*) be removed by CTF readers. Babeltrace
1.x (and possibly others) expect a perfect match between the names used
by tags and variants.

When the UNDERSCORE_PREFIXED_VARIANT_TAG_MAPPINGS quirk is enabled,
the variant's fields are modified to match the mappings of its tag.

From ABI version >= 10.x, the variant fields and tag mapping names
correctly match, making this work-around unnecessary.

However, since the variants produced by LTTng-UST contain TSDL-unsafe
names, a variant/selector sanitization pass is performed before
serializing a trace class hierarchy to TSDL.

The variant_tsdl_keyword_sanitizer visitor is used to visit field before
it is handed-over to the actual TSDL-producing visitor.

As it visits fields, the variant_tsdl_keyword_sanitizer populates a
"type_overrider" with TSDL-safe replacements for any variant or
enumeration that uses TSDL-unsafe identifiers (reserved keywords).

The type_overrider, in turn, is used by the rest of the TSDL
serialization visitor (tsdl_field_visitor) to swap any TSDL-unsafe types
with their sanitized version.

The tsdl_field_visitor owns the type_overrider and only briefly shares
it with the variant_tsdl_keyword_sanitizer which takes a reference to
it.

Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: Ib61eafc452338a99a02b9829cbd049cb6fa48ead
Depends-on: lttng-ust: Ia7e78096a9c31cd4c0574d599c961067d8f03791

src/bin/lttng-sessiond/field.cpp
src/bin/lttng-sessiond/field.hpp
src/bin/lttng-sessiond/tsdl-trace-class-visitor.cpp
src/bin/lttng-sessiond/tsdl-trace-class-visitor.hpp
src/bin/lttng-sessiond/ust-app.cpp
src/bin/lttng-sessiond/ust-app.hpp
src/common/Makefile.am
src/common/exception.cpp
src/common/exception.hpp
src/common/scope-exit.hpp [new file with mode: 0644]

index c00f6d1351085ec92b5bf7dd75c8ea6f5cf33fdc..e46e06631eae121b4736547b31995839783bd381 100644 (file)
@@ -110,6 +110,12 @@ lst::integer_type::integer_type(unsigned int in_alignment,
 {
 }
 
+lst::type::cuptr lst::integer_type::copy() const
+{
+       return lttng::make_unique<integer_type>(
+                       alignment, byte_order, size, signedness_, base_, roles_);
+}
+
 bool lst::integer_type::_is_equal(const type &base_other) const noexcept
 {
        const auto& other = static_cast<decltype(*this)&>(base_other);
@@ -162,6 +168,12 @@ lst::floating_point_type::floating_point_type(unsigned int in_alignment,
                                        typeid(*this)));
 }
 
+lst::type::cuptr lst::floating_point_type::copy() const
+{
+       return lttng::make_unique<floating_point_type>(
+                       alignment, byte_order, exponent_digits, mantissa_digits);
+}
+
 void lst::floating_point_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -255,6 +267,12 @@ bool lst::static_length_array_type::_is_equal(const type& base_other) const noex
        return array_type::_is_equal(base_other) && this->length == other.length;
 }
 
+lst::type::cuptr lst::static_length_array_type::copy() const
+{
+       return lttng::make_unique<static_length_array_type>(
+                       alignment, element_type->copy(), length);
+}
+
 void lst::static_length_array_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -276,6 +294,12 @@ bool lst::dynamic_length_array_type::_is_equal(const type& base_other) const noe
                        this->length_field_location == other.length_field_location;
 }
 
+lst::type::cuptr lst::dynamic_length_array_type::copy() const
+{
+       return lttng::make_unique<dynamic_length_array_type>(
+                       alignment, element_type->copy(), length_field_location);
+}
+
 void lst::dynamic_length_array_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -294,6 +318,11 @@ bool lst::static_length_blob_type::_is_equal(const type& base_other) const noexc
        return length_bytes == other.length_bytes && roles_ == other.roles_;
 }
 
+lst::type::cuptr lst::static_length_blob_type::copy() const
+{
+       return lttng::make_unique<static_length_blob_type>(alignment, length_bytes, roles_);
+}
+
 void lst::static_length_blob_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -312,6 +341,11 @@ bool lst::dynamic_length_blob_type::_is_equal(const type& base_other) const noex
        return length_field_location == other.length_field_location;
 }
 
+lst::type::cuptr lst::dynamic_length_blob_type::copy() const
+{
+       return lttng::make_unique<dynamic_length_blob_type>(alignment, length_field_location);
+}
+
 void lst::dynamic_length_blob_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -342,6 +376,11 @@ bool lst::static_length_string_type::_is_equal(const type& base_other) const noe
        return string_type::_is_equal(base_other) && this->length == other.length;
 }
 
+lst::type::cuptr lst::static_length_string_type::copy() const
+{
+       return lttng::make_unique<static_length_string_type>(alignment, encoding_, length);
+}
+
 void lst::static_length_string_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -363,6 +402,12 @@ bool lst::dynamic_length_string_type::_is_equal(const type& base_other) const no
                        this->length_field_location == other.length_field_location;
 }
 
+lst::type::cuptr lst::dynamic_length_string_type::copy() const
+{
+       return lttng::make_unique<dynamic_length_string_type>(
+                       alignment, encoding_, length_field_location);
+}
+
 void lst::dynamic_length_string_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -374,6 +419,11 @@ lst::null_terminated_string_type::null_terminated_string_type(unsigned int in_al
 {
 }
 
+lst::type::cuptr lst::null_terminated_string_type::copy() const
+{
+       return lttng::make_unique<null_terminated_string_type>(alignment, encoding_);
+}
+
 void lst::null_terminated_string_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
@@ -391,6 +441,19 @@ bool lst::structure_type::_is_equal(const type& base_other) const noexcept
        return fields_are_equal(this->fields_, other.fields_);
 }
 
+lst::type::cuptr lst::structure_type::copy() const
+{
+       structure_type::fields copy_of_fields;
+
+       copy_of_fields.reserve(fields_.size());
+       for (const auto& field : fields_) {
+               copy_of_fields.emplace_back(lttng::make_unique<lst::field>(
+                               field->name, field->get_type().copy()));
+       }
+
+       return lttng::make_unique<structure_type>(alignment, std::move(copy_of_fields));
+}
+
 void lst::structure_type::accept(type_visitor& visitor) const
 {
        visitor.visit(*this);
index a774d133a0978d0c59e0ce08f3a2865f675e23b4..2e3a67bf42f23f39a2b4242a1817096daff92778 100644 (file)
@@ -9,6 +9,7 @@
 #define LTTNG_FIELD_H
 
 #include <common/format.hpp>
+#include <common/make-unique.hpp>
 
 #include <vendor/optional.hpp>
 
@@ -65,6 +66,10 @@ public:
        bool operator==(const type& other) const noexcept;
        bool operator!=(const type& other) const noexcept;
        virtual ~type();
+
+       /* Obtain an independent copy of `type`. */
+       virtual type::cuptr copy() const = 0;
+
        virtual void accept(type_visitor& visitor) const = 0;
 
        const unsigned int alignment;
@@ -133,6 +138,8 @@ public:
                        base base,
                        roles roles = {});
 
+       virtual type::cuptr copy() const override;
+
        virtual void accept(type_visitor& visitor) const override;
 
        const enum byte_order byte_order;
@@ -157,6 +164,8 @@ public:
                        unsigned int exponent_digits,
                        unsigned int mantissa_digits);
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const enum byte_order byte_order;
@@ -236,7 +245,7 @@ bool operator==(const enumeration_mapping<MappingIntegerType>& lhs,
 }
 } /* namespace details */
 
-template <class MappingIntegerType>
+template <typename MappingIntegerType>
 class typed_enumeration_type : public enumeration_type {
 public:
        using mapping = details::enumeration_mapping<MappingIntegerType>;
@@ -264,6 +273,12 @@ public:
        {
        }
 
+       virtual type::cuptr copy() const override
+       {
+               return lttng::make_unique<typed_enumeration_type<MappingIntegerType>>(
+                               alignment, byte_order, size, base_, mappings_, roles_);
+       }
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const std::shared_ptr<const mappings> mappings_;
@@ -298,6 +313,8 @@ public:
                        type::cuptr element_type,
                        uint64_t in_length);
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const uint64_t length;
@@ -312,6 +329,8 @@ public:
                        type::cuptr element_type,
                        field_location length_field_location);
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const field_location length_field_location;
@@ -331,6 +350,8 @@ public:
 
        static_length_blob_type(unsigned int alignment, uint64_t in_length_bytes, roles roles = {});
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const uint64_t length_bytes;
@@ -344,6 +365,8 @@ class dynamic_length_blob_type : public type {
 public:
        dynamic_length_blob_type(unsigned int alignment, field_location length_field_location);
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const field_location length_field_location;
@@ -376,6 +399,9 @@ class static_length_string_type : public string_type {
 public:
        static_length_string_type(
                        unsigned int alignment, enum encoding in_encoding, uint64_t length);
+
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const uint64_t length;
@@ -389,6 +415,9 @@ public:
        dynamic_length_string_type(unsigned int alignment,
                        enum encoding in_encoding,
                        field_location length_field_location);
+
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const field_location length_field_location;
@@ -400,6 +429,9 @@ private:
 class null_terminated_string_type : public string_type {
 public:
        null_terminated_string_type(unsigned int alignment, enum encoding in_encoding);
+
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 };
 
@@ -409,6 +441,8 @@ public:
 
        structure_type(unsigned int alignment, fields in_fields);
 
+       virtual type::cuptr copy() const override final;
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const fields fields_;
@@ -417,7 +451,7 @@ private:
        virtual bool _is_equal(const type& base_other) const noexcept override final;
 };
 
-template <class MappingIntegerType>
+template <typename MappingIntegerType>
 class variant_type : public type {
        static_assert(std::is_same<MappingIntegerType,
                                        unsigned_enumeration_type::mapping::range_t::
@@ -440,11 +474,24 @@ public:
        {
        }
 
+       virtual type::cuptr copy() const override final
+       {
+               choices copy_of_choices;
+
+               copy_of_choices.reserve(choices_.size());
+               for (const auto& current_choice : choices_) {
+                       copy_of_choices.emplace_back(
+                                       current_choice.first, current_choice.second->copy());
+               }
+
+               return lttng::make_unique<variant_type<MappingIntegerType>>(
+                       alignment, selector_field_location, std::move(copy_of_choices));
+       }
+
        virtual void accept(type_visitor& visitor) const override final;
 
        const field_location selector_field_location;
        const choices choices_;
-;
 
 private:
        static bool _choices_are_equal(const choices& a, const choices& b)
@@ -507,6 +554,8 @@ protected:
 } /* namespace lttng */
 
 /*
+ * Field formatters for libfmt.
+ *
  * Due to a bug in g++ < 7.1, this specialization must be enclosed in the fmt namespace,
  * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480.
  */
@@ -548,6 +597,52 @@ struct formatter<lttng::sessiond::trace::field_location> : formatter<std::string
                return format_to(ctx.out(), location_str);
        }
 };
+
+namespace details {
+template <typename MappingIntegerType>
+::std::string format_mapping_range(typename lttng::sessiond::trace::typed_enumeration_type<
+               MappingIntegerType>::mapping::range_t range)
+{
+       if (range.begin == range.end) {
+               return ::fmt::format("[{}]", range.begin);
+       } else {
+               return ::fmt::format("[{}, {}]", range.begin, range.end);
+       }
+}
+} /* namespace details */
+
+template <>
+struct formatter<typename lttng::sessiond::trace::signed_enumeration_type::mapping::range_t>
+       : formatter<std::string> {
+       template <typename FormatCtx>
+       typename FormatCtx::iterator
+       format(typename lttng::sessiond::trace::signed_enumeration_type::mapping::range_t range,
+                       FormatCtx& ctx)
+       {
+               return format_to(ctx.out(),
+                               details::format_mapping_range<
+                                               lttng::sessiond::trace::signed_enumeration_type::
+                                                               mapping::range_t::range_integer_t>(
+                                               range));
+       }
+};
+
+template <>
+struct formatter<typename lttng::sessiond::trace::unsigned_enumeration_type::mapping::range_t>
+       : formatter<std::string> {
+       template <typename FormatCtx>
+       typename FormatCtx::iterator
+       format(typename lttng::sessiond::trace::unsigned_enumeration_type::mapping::range_t range,
+                       FormatCtx& ctx)
+       {
+               return format_to(ctx.out(),
+                               details::format_mapping_range<
+                                               lttng::sessiond::trace::unsigned_enumeration_type::
+                                                               mapping::range_t::range_integer_t>(
+                                               range));
+       }
+};
+
 } /* namespace fmt */
 
 #endif /* LTTNG_FIELD_H */
index d240e1fb050837aa65d3e52bc7c4e6c8e0405d7c..ac1be1bf2233cf2d186b212762e6f1a4069b3cab 100644 (file)
@@ -12,6 +12,7 @@
 #include <common/format.hpp>
 #include <common/make-unique.hpp>
 #include <common/uuid.hpp>
+#include <common/scope-exit.hpp>
 
 #include <vendor/optional.hpp>
 
@@ -21,6 +22,7 @@
 #include <queue>
 #include <set>
 #include <stack>
+#include <unordered_set>
 
 namespace lst = lttng::sessiond::trace;
 namespace tsdl = lttng::sessiond::tsdl;
@@ -33,7 +35,7 @@ const auto ctf_spec_minor = 8;
  * Although the CTF v1.8 specification recommends ignoring any leading underscore, Some readers,
  * such as Babeltrace 1.x, expect special identifiers without a prepended underscore.
  */
-const std::set<std::string> safe_tsdl_identifiers = {
+const std::unordered_set<std::string> safe_tsdl_identifiers = {
        "stream_id",
        "packet_size",
        "content_size",
@@ -112,11 +114,276 @@ std::string escape_tsdl_env_string_value(const std::string& original_string)
        return escaped_string;
 }
 
+/*
+ * Variants produced by LTTng-UST contain TSDL-unsafe names. A variant/selector
+ * sanitization pass is performed before serializing a trace class hierarchy to
+ * TSDL.
+ *
+ * The variant_tsdl_keyword_sanitizer visitor is used to visit field before it
+ * is handed-over to the actual TSDL-producing visitor.
+ *
+ * As it visits fields, the variant_tsdl_keyword_sanitizer populates a
+ * "type_overrider" with TSDL-safe replacements for any variant or enumeration
+ * that uses TSDL-unsafe identifiers (reserved keywords).
+ *
+ * The type_overrider, in turn, is used by the rest of the TSDL serialization
+ * visitor (tsdl_field_visitor) to swap any TSDL-unsafe types with their
+ * sanitized version.
+ *
+ * The tsdl_field_visitor owns the type_overrider and only briefly shares it
+ * with the variant_tsdl_keyword_sanitizer which takes a reference to it.
+ */
+class variant_tsdl_keyword_sanitizer : public lttng::sessiond::trace::field_visitor,
+                                      public lttng::sessiond::trace::type_visitor {
+public:
+       using type_lookup_function = std::function<const lst::type&(const lst::field_location&)>;
+
+       variant_tsdl_keyword_sanitizer(tsdl::details::type_overrider& type_overrides,
+                       type_lookup_function lookup_type) :
+               _type_overrides{type_overrides}, _lookup_type(lookup_type)
+       {
+       }
+
+private:
+       class _c_string_comparator {
+       public:
+               int operator()(const char *lhs, const char *rhs) const
+               {
+                       return std::strcmp(lhs, rhs) < 0;
+               }
+       };
+       using unsafe_names = std::set<const char *, _c_string_comparator>;
+
+       virtual void visit(const lst::field& field) override final
+       {
+               _type_overrides.type(field.get_type()).accept(*this);
+       }
+
+       virtual void visit(const lst::integer_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::floating_point_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::signed_enumeration_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::unsigned_enumeration_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::static_length_array_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::dynamic_length_array_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::static_length_blob_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::dynamic_length_blob_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::null_terminated_string_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::structure_type& type) override final
+       {
+               /* Recurse into structure attributes. */
+               for (const auto& field : type.fields_) {
+                       field->accept(*this);
+               }
+       }
+
+       /*
+        * Create a new enumeration type replacing any mapping that match, by name, the elements in `unsafe_names_found`
+        * with a TSDL-safe version. Currently, unsafe identifiers are made safe by adding
+        * a leading underscore.
+        */
+       template <typename MappingIntegerType>
+       lst::type::cuptr _create_sanitized_selector(
+                       const lst::typed_enumeration_type<MappingIntegerType>& original_selector,
+                       const unsafe_names& unsafe_names_found)
+       {
+               auto new_mappings = std::make_shared<typename lst::typed_enumeration_type<
+                               MappingIntegerType>::mappings>();
+
+               for (const auto& mapping : *original_selector.mappings_) {
+                       if (unsafe_names_found.find(mapping.name.c_str()) ==
+                                       unsafe_names_found.end()) {
+                               /* Mapping is safe, simply copy it. */
+                               new_mappings->emplace_back(mapping);
+                       } else {
+                               /* Unsafe mapping, rename it and keep the rest of its attributes. */
+                               new_mappings->emplace_back(
+                                               fmt::format("_{}", mapping.name), mapping.range);
+                       }
+               }
+
+               return lttng::make_unique<lst::typed_enumeration_type<MappingIntegerType>>(
+                               original_selector.alignment, original_selector.byte_order,
+                               original_selector.size, original_selector.base_, new_mappings);
+       }
+
+       template <typename MappingIntegerType>
+       const typename lst::typed_enumeration_type<MappingIntegerType>::mapping&
+       _find_enumeration_mapping_by_range(
+                       const typename lst::typed_enumeration_type<MappingIntegerType>&
+                                       enumeration_type,
+                       const typename lst::typed_enumeration_type<
+                                       MappingIntegerType>::mapping::range_t& target_mapping_range)
+       {
+               for (const auto& mapping : *enumeration_type.mappings_) {
+                       if (mapping.range == target_mapping_range) {
+                               return mapping;
+                       }
+               }
+
+               LTTNG_THROW_ERROR(fmt::format(
+                               "Failed to find mapping by range in enumeration while sanitizing a variant: target_mapping_range={}",
+                               target_mapping_range));
+       }
+
+       /*
+        * Copy `original_variant`, but use the mappings of a previously-published sanitized tag
+        * to produce a TSDL-safe version of the variant.
+        */
+       template <typename MappingIntegerType>
+       lst::type::cuptr _create_sanitized_variant(
+                       const lst::variant_type<MappingIntegerType>& original_variant)
+       {
+               typename lst::variant_type<MappingIntegerType>::choices new_choices;
+               const auto& sanitized_selector = static_cast<
+                               const lst::typed_enumeration_type<MappingIntegerType>&>(
+                               _type_overrides.type(_lookup_type(
+                                               original_variant.selector_field_location)));
+
+               /* Visit variant choices to sanitize them as needed. */
+               for (const auto& choice : original_variant.choices_) {
+                       choice.second->accept(*this);
+               }
+
+               for (const auto& choice : original_variant.choices_) {
+                       const auto& sanitized_choice_type = _type_overrides.type(*choice.second);
+
+                       new_choices.emplace_back(
+                                       _find_enumeration_mapping_by_range(
+                                                       sanitized_selector, choice.first.range),
+                                       sanitized_choice_type.copy());
+               }
+
+               return lttng::make_unique<lst::variant_type<MappingIntegerType>>(
+                                              original_variant.alignment,
+                                              original_variant.selector_field_location,
+                                              std::move(new_choices));
+       }
+
+       template <typename MappingIntegerType>
+       void visit_variant(const lst::variant_type<MappingIntegerType>& type)
+       {
+               unsafe_names unsafe_names_found;
+               static const std::unordered_set<std::string> tsdl_protected_keywords = {
+                               "align",
+                               "callsite",
+                               "const",
+                               "char",
+                               "clock",
+                               "double",
+                               "enum",
+                               "env",
+                               "event",
+                               "floating_point",
+                               "float",
+                               "integer",
+                               "int",
+                               "long",
+                               "short",
+                               "signed",
+                               "stream",
+                               "string",
+                               "struct",
+                               "trace",
+                               "typealias",
+                               "typedef",
+                               "unsigned",
+                               "variant",
+                               "void",
+                               "_Bool",
+                               "_Complex",
+                               "_Imaginary",
+               };
+
+               for (const auto& choice : type.choices_) {
+                       if (tsdl_protected_keywords.find(choice.first.name) != tsdl_protected_keywords.cend()) {
+                               /* Choice name is illegal, we have to rename it and its matching mapping. */
+                               unsafe_names_found.insert(choice.first.name.c_str());
+                       }
+               }
+
+               if (unsafe_names_found.empty()) {
+                       return;
+               }
+
+               /*
+                * Look-up selector field type.
+                *
+                * Since it may have been overriden previously, keep the original and overriden
+                * selector field types (which may be the same, if the original was not overriden).
+                *
+                * We work from the "overriden" selector field type to preserve any existing
+                * modifications. However, the original field type will be used to publish the new
+                * version of the type leaving only the most recent overriden type in the type
+                * overrides.
+                */
+               const auto& original_selector_type = _lookup_type(type.selector_field_location);
+               const auto& overriden_selector_type = _type_overrides.type(original_selector_type);
+
+               auto sanitized_selector_type = _create_sanitized_selector(
+                               static_cast<const lst::typed_enumeration_type<MappingIntegerType>&>(
+                                       overriden_selector_type), unsafe_names_found);
+               _type_overrides.publish(original_selector_type, std::move(sanitized_selector_type));
+
+               auto sanitized_variant_type = _create_sanitized_variant(
+                               static_cast<const lst::variant_type<MappingIntegerType>&>(type));
+               _type_overrides.publish(type, std::move(sanitized_variant_type));
+       }
+
+       virtual void visit(const lst::variant_type<lst::signed_enumeration_type::mapping::range_t::range_integer_t>& type) override final
+       {
+               visit_variant(type);
+       }
+
+       virtual void visit(const lst::variant_type<lst::unsigned_enumeration_type::mapping::range_t::range_integer_t>& type) override final
+       {
+               visit_variant(type);
+       }
+
+       virtual void visit(const lst::static_length_string_type& type __attribute__((unused))) override final
+       {
+       }
+
+       virtual void visit(const lst::dynamic_length_string_type& type __attribute__((unused))) override final
+       {
+       }
+
+       tsdl::details::type_overrider& _type_overrides;
+       const type_lookup_function _lookup_type;
+};
+
 class tsdl_field_visitor : public lttng::sessiond::trace::field_visitor,
                           public lttng::sessiond::trace::type_visitor {
 public:
        tsdl_field_visitor(const lst::abi& abi,
                        unsigned int indentation_level,
+                       const tsdl::details::type_overrider& type_overrides,
                        const nonstd::optional<std::string>& in_default_clock_class_name =
                                        nonstd::nullopt) :
                _indentation_level{indentation_level},
@@ -124,7 +391,8 @@ public:
                _bypass_identifier_escape{false},
                _default_clock_class_name{in_default_clock_class_name ?
                                                in_default_clock_class_name->c_str() :
-                                               nullptr}
+                                               nullptr},
+               _type_overrides{type_overrides}
        {
        }
 
@@ -146,8 +414,7 @@ private:
                 */
                _current_field_name.push(_bypass_identifier_escape ?
                                field.name : escape_tsdl_identifier(field.name));
-
-               field.get_type().accept(*this);
+               _type_overrides.type(field.get_type()).accept(*this);
                _description += " ";
                _description += _current_field_name.top();
                _current_field_name.pop();
@@ -267,10 +534,8 @@ private:
                /* name follows, when applicable. */
                _description += "enum : ";
 
-               tsdl_field_visitor integer_visitor{_trace_abi, _indentation_level};
-
-               integer_visitor.visit(static_cast<const lst::integer_type&>(type));
-               _description += integer_visitor.transfer_description() + " {\n";
+               visit(static_cast<const lst::integer_type&>(type));
+               _description += " {\n";
 
                const auto mappings_indentation_level = _indentation_level + 1;
 
@@ -511,6 +776,7 @@ private:
 
        bool _bypass_identifier_escape;
        const char *_default_clock_class_name;
+       const tsdl::details::type_overrider& _type_overrides;
 };
 
 class tsdl_trace_environment_visitor : public lst::trace_class_environment_visitor {
@@ -556,7 +822,11 @@ void tsdl::trace_class_visitor::append_metadata_fragment(const std::string& frag
 
 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class& trace_class)
 {
-       tsdl_field_visitor packet_header_visitor(trace_class.abi, 1);
+       /* Ensure this instance is not used against multiple trace classes. */
+       LTTNG_ASSERT(!_current_trace_class || _current_trace_class == &trace_class);
+       _current_trace_class = &trace_class;
+
+       tsdl_field_visitor packet_header_visitor{trace_class.abi, 1, _sanitized_types_overrides};
 
        trace_class.get_packet_header()->accept(packet_header_visitor);
 
@@ -621,14 +891,24 @@ void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class&
 
 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class& stream_class)
 {
+       _current_stream_class = &stream_class;
+       const auto clear_stream_class_on_exit = lttng::make_scope_exit(
+                       [this]() noexcept { _current_stream_class = nullptr; });
+
        auto stream_class_str = fmt::format("stream {{\n"
                "       id = {};\n", stream_class.id);
+       variant_tsdl_keyword_sanitizer variant_sanitizer(_sanitized_types_overrides,
+                       [this](const lttng::sessiond::trace::field_location& location)
+                                       -> const lst::type& {
+                               return _lookup_field_type(location);
+                       });
 
        const auto *event_header = stream_class.get_event_header();
        if (event_header) {
-               auto event_header_visitor = tsdl_field_visitor(
-                               _trace_abi, 1, stream_class.default_clock_class_name);
+               tsdl_field_visitor event_header_visitor{_trace_abi, 1, _sanitized_types_overrides,
+                               stream_class.default_clock_class_name};
 
+               event_header->accept(variant_sanitizer);
                event_header->accept(event_header_visitor);
                stream_class_str += fmt::format("       event.header := {};\n",
                                event_header_visitor.transfer_description());
@@ -636,9 +916,10 @@ void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class
 
        const auto *packet_context = stream_class.get_packet_context();
        if (packet_context) {
-               auto packet_context_visitor = tsdl_field_visitor(
-                               _trace_abi, 1, stream_class.default_clock_class_name);
+               tsdl_field_visitor packet_context_visitor{_trace_abi, 1, _sanitized_types_overrides,
+                               stream_class.default_clock_class_name};
 
+               packet_context->accept(variant_sanitizer);
                packet_context->accept(packet_context_visitor);
                stream_class_str += fmt::format("       packet.context := {};\n",
                                packet_context_visitor.transfer_description());
@@ -646,8 +927,9 @@ void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class
 
        const auto *event_context = stream_class.get_event_context();
        if (event_context) {
-               auto event_context_visitor = tsdl_field_visitor(_trace_abi, 1);
+               tsdl_field_visitor event_context_visitor{_trace_abi, 1, _sanitized_types_overrides};
 
+               event_context->accept(variant_sanitizer);
                event_context->accept(event_context_visitor);
                stream_class_str += fmt::format("       event.context := {};\n",
                                event_context_visitor.transfer_description());
@@ -660,6 +942,10 @@ void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class
 
 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class& event_class)
 {
+       _current_event_class = &event_class;
+       const auto clear_event_class_on_exit = lttng::make_scope_exit(
+                       [this]() noexcept { _current_event_class = nullptr; });
+
        auto event_class_str = fmt::format("event {{\n"
                                           "    name = \"{name}\";\n"
                                           "    id = {id};\n"
@@ -675,12 +961,155 @@ void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class&
                                "       model.emf.uri = \"{}\";\n", *event_class.model_emf_uri);
        }
 
-       auto payload_visitor = tsdl_field_visitor(_trace_abi, 1);
+       tsdl_field_visitor payload_visitor{_trace_abi, 1, _sanitized_types_overrides};
+       variant_tsdl_keyword_sanitizer variant_sanitizer(_sanitized_types_overrides,
+                       [this](const lttng::sessiond::trace::field_location& location)
+                                       -> const lst::type& {
+                               return _lookup_field_type(location);
+                       });
 
-       event_class.payload->accept(static_cast<lst::type_visitor&>(payload_visitor));
+       event_class.payload->accept(variant_sanitizer);
+       event_class.payload->accept(payload_visitor);
 
        event_class_str += fmt::format(
                        "       fields := {};\n}};\n\n", payload_visitor.transfer_description());
 
        append_metadata_fragment(event_class_str);
 }
+
+void tsdl::details::type_overrider::publish(
+               const lttng::sessiond::trace::type& original_type,
+               lttng::sessiond::trace::type::cuptr new_type_override)
+{
+       auto current_override = _overriden_types.find(&original_type);
+
+       if (current_override != _overriden_types.end()) {
+               current_override->second = std::move(new_type_override);
+       } else {
+               _overriden_types.insert(std::make_pair(&original_type, std::move(new_type_override)));
+       }
+}
+
+const lst::type& tsdl::details::type_overrider::type(
+               const lttng::sessiond::trace::type& original) const noexcept
+{
+       const auto result = _overriden_types.find(&original);
+
+       if (result != _overriden_types.end()) {
+               /* Provide the overriden type. */
+               return *result->second;
+       }
+
+       /* Pass the original type through. */
+       return original;
+}
+
+namespace {
+const lttng::sessiond::trace::type& lookup_type_from_root_type(
+               const lttng::sessiond::trace::type& root_type,
+               const lttng::sessiond::trace::field_location& field_location)
+{
+       const auto *type = &root_type;
+
+       for (const auto& location_element : field_location.elements_) {
+               /* Only structures can be traversed. */
+               const auto *struct_type = dynamic_cast<const lst::structure_type *>(type);
+
+               /*
+                * Traverse the type by following the field location path.
+                *
+                * While field paths are assumed to have been validated before-hand,
+                * a dynamic cast is performed here as an additional precaution
+                * since none of this is performance-critical; it can be removed
+                * safely.
+                */
+               if (!struct_type) {
+                       LTTNG_THROW_ERROR(fmt::format(
+                                       "Encountered a type that is not a structure while traversing field location: field-location=`{}`",
+                                       field_location));
+               }
+
+               const auto field_found_it = std::find_if(struct_type->fields_.cbegin(),
+                               struct_type->fields_.cend(),
+                               [&location_element](const lst::field::cuptr& struct_field) {
+                                       return struct_field->name == location_element;
+                               });
+
+               if (field_found_it == struct_type->fields_.cend()) {
+                       LTTNG_THROW_ERROR(fmt::format(
+                                       "Failed to find field using field location: field-name:=`{field_name}`, field-location=`{field_location}`",
+                                       fmt::arg("field_location", field_location),
+                                       fmt::arg("field_name", location_element)));
+               }
+
+               type = &(*field_found_it)->get_type();
+       }
+
+       return *type;
+}
+} /* anonymous namespace. */
+
+/*
+ * The trace hierarchy is assumed to have been validated on creation.
+ * This function can only fail due to a validation error, hence
+ * why it throws on any unexpected/invalid field location.
+ *
+ * Does not return an overriden field type; it returns the original field type
+ * as found in the trace hierarchy.
+ */
+const lttng::sessiond::trace::type& lttng::sessiond::tsdl::trace_class_visitor::_lookup_field_type(
+               const lttng::sessiond::trace::field_location& location) const
+{
+       /* Validate the look-up is happening in a valid visit context. */
+       switch (location.root_) {
+       case lst::field_location::root::EVENT_RECORD_HEADER:
+       case lst::field_location::root::EVENT_RECORD_PAYLOAD:
+               if (!_current_event_class) {
+                       LTTNG_THROW_ERROR(
+                                       "Field type look-up failure: no current event class in visitor's context");
+               }
+               /* fall through. */
+       case lst::field_location::root::EVENT_RECORD_COMMON_CONTEXT:
+       case lst::field_location::root::PACKET_CONTEXT:
+               if (!_current_stream_class) {
+                       LTTNG_THROW_ERROR(
+                                       "Field type look-up failure: no current stream class in visitor's context");
+               }
+               /* fall through. */
+       case lst::field_location::root::PACKET_HEADER:
+               if (!_current_trace_class) {
+                       LTTNG_THROW_ERROR(
+                                       "Field type look-up failure: no current trace class in visitor's context");
+               }
+
+               break;
+       case lst::field_location::root::EVENT_RECORD_SPECIFIC_CONTEXT:
+               LTTNG_THROW_UNSUPPORTED_ERROR(
+                               "Field type look-up failure: event-record specific contexts are not supported");
+       default:
+               LTTNG_THROW_UNSUPPORTED_ERROR(
+                               "Field type look-up failure: unknown field location root");
+       }
+
+       switch (location.root_) {
+       case lst::field_location::root::PACKET_HEADER:
+               return lookup_type_from_root_type(
+                               *_current_trace_class->get_packet_header(), location);
+       case lst::field_location::root::PACKET_CONTEXT:
+               return lookup_type_from_root_type(
+                               *_current_stream_class->get_packet_context(), location);
+       case lst::field_location::root::EVENT_RECORD_HEADER:
+               return lookup_type_from_root_type(
+                               *_current_stream_class->get_event_header(), location);
+       case lst::field_location::root::EVENT_RECORD_COMMON_CONTEXT:
+               return lookup_type_from_root_type(
+                               *_current_stream_class->get_event_context(), location);
+       case lst::field_location::root::EVENT_RECORD_PAYLOAD:
+               return lookup_type_from_root_type(
+                               *_current_event_class->payload, location);
+       case lst::field_location::root::EVENT_RECORD_SPECIFIC_CONTEXT:
+       default:
+               /* Unreachable as it was checked before. */
+               abort();
+       }
+}
index d06ea9983afd4871d306fde9f56ed92e7670f48f..1b8bd642de9b885614972a19250da54824b05e2b 100644 (file)
@@ -15,6 +15,7 @@
 #include <vendor/optional.hpp>
 
 #include <functional>
+#include <unordered_map>
 
 namespace lttng {
 namespace sessiond {
@@ -22,6 +23,36 @@ namespace tsdl {
 
 using append_metadata_fragment_function = std::function<void(const std::string& fragment)>;
 
+namespace details {
+/*
+ * Register types to be overriden. For example, a TSDL-safe copy of a type can
+ * be added to be overriden whenever the original type is encountered.
+ *
+ * Note that this class assumes no ownership of the original types. It assumes
+ * that the original types live as long as the original trace.
+ */
+class type_overrider {
+public:
+       type_overrider() = default;
+
+       void publish(const lttng::sessiond::trace::type& original,
+                       lttng::sessiond::trace::type::cuptr new_type_override);
+       const lttng::sessiond::trace::type& type(
+                       const lttng::sessiond::trace::type& original) const noexcept;
+
+private:
+       std::unordered_map<const lttng::sessiond::trace::type *, lttng::sessiond::trace::type::cuptr>
+                       _overriden_types;
+};
+} /* namespace details. */
+
+/*
+ * TSDL-producing trace class visitor.
+ *
+ * An instance of this class must not be used on multiple trace class instances.
+ * The `append_metadata` callback is automatically invoked when a coherent
+ * fragment of TSDL is available.
+ */
 class trace_class_visitor : public lttng::sessiond::trace::trace_class_visitor {
 public:
        trace_class_visitor(const lttng::sessiond::trace::abi& trace_abi,
@@ -35,9 +66,30 @@ public:
 private:
        /* Coherent (parseable) fragments must be appended. */
        void append_metadata_fragment(const std::string& fragment) const;
+       const lttng::sessiond::trace::type& _lookup_field_type(
+                       const lttng::sessiond::trace::field_location& field_location) const;
 
        const lttng::sessiond::trace::abi& _trace_abi;
        const append_metadata_fragment_function _append_metadata_fragment;
+       details::type_overrider _sanitized_types_overrides;
+
+       /*
+        * Current visit context.
+        *
+        * The members of a trace class hierarchy do not provide back-references
+        * up the hierarchy (e.g. stream class to its parent trace class).
+        *
+        * This context allows the visitor to evaluate a "field location".
+        *
+        * _current_trace_class is set the first time a trace class is visited and
+        * remains valid until the destruction of this object.
+        *
+        * _current_stream_class and _current_event_class are set only in the
+        * context of the visit of a stream class and of its event class(es).
+        */
+       const lttng::sessiond::trace::trace_class *_current_trace_class = nullptr;
+       const lttng::sessiond::trace::stream_class *_current_stream_class = nullptr;
+       const lttng::sessiond::trace::event_class *_current_event_class = nullptr;
 };
 
 } /* namespace tsdl */
index 8addb5edacabb5bd9ed2397ab81f276cf5b078fb..11d604ea9a463e7c32126b5189e46707e74f7ce1 100644 (file)
@@ -7895,3 +7895,30 @@ error:
        rcu_read_unlock();
        return ret;
 }
+
+lsu::ctl_field_quirks ust_app::ctl_field_quirks() const
+{
+       /*
+        * Application contexts are expressed as variants. LTTng-UST announces
+        * those by registering an enumeration named `..._tag`. It then registers a
+        * variant as part of the event context that contains the various possible
+        * types.
+        *
+        * Unfortunately, the names used in the enumeration and variant don't
+        * match: the enumeration names are all prefixed with an underscore while
+        * the variant type tag fields aren't.
+        *
+        * While the CTF 1.8.3 specification mentions that
+        * underscores *should* (not *must*) be removed by CTF readers. Babeltrace
+        * 1.x (and possibly others) expect a perfect match between the names used
+        * by tags and variants.
+        *
+        * When the UNDERSCORE_PREFIXED_VARIANT_TAG_MAPPINGS quirk is enabled,
+        * the variant's fields are modified to match the mappings of its tag.
+        *
+        * From ABI version >= 10.x, the variant fields and tag mapping names
+        * correctly match, making this quirk unnecessary.
+        */
+       return v_major <= 9 ? lsu::ctl_field_quirks::UNDERSCORE_PREFIXED_VARIANT_TAG_MAPPINGS :
+               lsu::ctl_field_quirks::NONE;
+}
\ No newline at end of file
index c48e2b9e25b97f95e745b4cbd47d667583c0eb26..d0cbd217ce857aa48c0e7164e2143de66e2f7986 100644 (file)
@@ -19,6 +19,7 @@
 #include "ust-registry.hpp"
 #include "ust-registry-session.hpp"
 #include "session.hpp"
+#include "ust-field-convert.hpp"
 
 #define UST_APP_EVENT_LIST_SIZE 32
 
@@ -324,6 +325,8 @@ struct ust_app {
         * (ust_app_event_notifier_rule) by their token's value.
         */
        struct lttng_ht *token_to_event_notifier_rule_ht;
+
+       lttng::sessiond::ust::ctl_field_quirks ctl_field_quirks() const;
 };
 
 /*
index 165b7c3a16c22631a4a1dd2e7dac351b25838ab5..39c531cbafd0ef4a83e270794e506b542a7f04fe 100644 (file)
@@ -102,6 +102,7 @@ libcommon_lgpl_la_SOURCES = \
        random.cpp random.hpp \
        readwrite.cpp readwrite.hpp \
        runas.cpp runas.hpp \
+       scope-exit.hpp \
        session-descriptor.cpp \
        snapshot.cpp snapshot.hpp \
        spawn-viewer.cpp spawn-viewer.hpp \
index f1c530f7e4249928541a7603f311dcaac2cbee82..5645d7d91702efe3a0df4779d363fe616acbb78a 100644 (file)
@@ -50,6 +50,14 @@ lttng::runtime_error::runtime_error(const std::string& msg,
 {
 }
 
+lttng::unsupported_error::unsupported_error(const std::string& msg,
+               const char *file_name,
+               const char *function_name,
+               unsigned int line_number) :
+       std::runtime_error(msg + " " + format_throw_location(file_name, function_name, line_number))
+{
+}
+
 lttng::communication_error::communication_error(const std::string& msg,
                const char *file_name,
                const char *function_name,
index b7e8261ed65217f5652fd88192c37e78c178dd76..30a40347050e8839c210e5bb461540f41186a92f 100644 (file)
@@ -20,6 +20,8 @@
        throw lttng::posix_error(msg, errno_code, __FILE__, __func__, __LINE__)
 #define LTTNG_THROW_ERROR(msg) \
        throw lttng::runtime_error(msg, __FILE__, __func__, __LINE__)
+#define LTTNG_THROW_UNSUPPORTED_ERROR(msg) \
+       throw lttng::runtime_error(msg, __FILE__, __func__, __LINE__)
 #define LTTNG_THROW_COMMUNICATION_ERROR(msg) \
        throw lttng::communication_error(msg, __FILE__, __func__, __LINE__)
 #define LTTNG_THROW_PROTOCOL_ERROR(msg) \
@@ -36,6 +38,14 @@ public:
                        unsigned int line_number);
 };
 
+class unsupported_error : public std::runtime_error {
+public:
+       explicit unsupported_error(const std::string& msg,
+                       const char *file_name,
+                       const char *function_name,
+                       unsigned int line_number);
+};
+
 namespace ctl {
 /* Wrap lttng_error_code errors which may be reported through liblttng-ctl's interface. */
 class error : public runtime_error {
diff --git a/src/common/scope-exit.hpp b/src/common/scope-exit.hpp
new file mode 100644 (file)
index 0000000..46479bc
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#ifndef LTTNG_SCOPE_EXIT_H
+#define LTTNG_SCOPE_EXIT_H
+
+#include <utility>
+
+namespace lttng {
+
+namespace details {
+/* Is operator() of InvocableType is marked as noexcept? */
+template <typename InvocableType>
+struct is_invocation_noexcept
+       : std::integral_constant<bool, noexcept((std::declval<InvocableType>())())> {
+};
+} /* namespace details. */
+
+/*
+ * Generic utility to run a lambda (or any other invocable object) when leaving
+ * a scope.
+ *
+ * Notably, this makes it easy to specify an action (e.g. restore a context)
+ * that must occur at the end of a function or roll-back operations in an
+ * exception-safe way.
+ */
+template <typename ScopeExitInvocableType>
+class scope_exit {
+public:
+       /*
+        * Since ScopeExitInvocableType will be invoked in the destructor, it
+        * must be `noexcept` lest we anger the undefined behaviour gods.
+        */
+       static_assert(details::is_invocation_noexcept<ScopeExitInvocableType>::value,
+                       "scope_exit requires a noexcept invocable type");
+
+       explicit scope_exit(ScopeExitInvocableType&& scope_exit_callable) :
+               _on_scope_exit{std::forward<ScopeExitInvocableType>(scope_exit_callable)}
+       {
+       }
+
+       scope_exit(scope_exit&& rhs) :
+               _on_scope_exit{std::move(rhs._on_scope_exit)}, _armed{rhs._armed}
+       {
+               /* Don't invoke ScopeExitInvocableType for the moved-from copy. */
+               rhs.disarm();
+       }
+
+       /*
+        * The copy constructor is disabled to prevent the action from being
+        * executed twice should a copy be performed accidentaly.
+        *
+        * The move-constructor is present to enable make_scope_exit() but to
+        * also propagate the scope_exit to another scope, should it be needed.
+        */
+       scope_exit(const scope_exit&) = delete;
+       scope_exit() = delete;
+
+       void disarm() noexcept
+       {
+               _armed = false;
+       }
+
+       ~scope_exit()
+       {
+               if (_armed) {
+                       _on_scope_exit();
+               }
+       }
+
+private:
+       ScopeExitInvocableType _on_scope_exit;
+       bool _armed = true;
+};
+
+template <typename ScopeExitInvocableType>
+scope_exit<ScopeExitInvocableType> make_scope_exit(ScopeExitInvocableType&& scope_exit_callable)
+{
+       return scope_exit<ScopeExitInvocableType>(
+                       std::forward<ScopeExitInvocableType>(scope_exit_callable));
+}
+
+} /* namespace lttng */
+
+#endif /* LTTNG_SCOPE_EXIT_H */
This page took 0.041281 seconds and 4 git commands to generate.