From: Jérémie Galarneau Date: Mon, 28 Nov 2022 23:02:44 +0000 (-0500) Subject: sessiond: ust: conditionally enable the underscore prefix variant quirk X-Git-Url: https://git.lttng.org./?a=commitdiff_plain;h=b6bbb1d666531bf061f29884da1b0d7c10f59aa0;p=lttng-tools.git sessiond: ust: conditionally enable the underscore prefix variant quirk 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 Change-Id: Ib61eafc452338a99a02b9829cbd049cb6fa48ead Depends-on: lttng-ust: Ia7e78096a9c31cd4c0574d599c961067d8f03791 --- diff --git a/src/bin/lttng-sessiond/field.cpp b/src/bin/lttng-sessiond/field.cpp index c00f6d135..e46e06631 100644 --- a/src/bin/lttng-sessiond/field.cpp +++ b/src/bin/lttng-sessiond/field.cpp @@ -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( + alignment, byte_order, size, signedness_, base_, roles_); +} + bool lst::integer_type::_is_equal(const type &base_other) const noexcept { const auto& other = static_cast(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( + 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( + 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( + 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(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(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(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( + 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(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( + field->name, field->get_type().copy())); + } + + return lttng::make_unique(alignment, std::move(copy_of_fields)); +} + void lst::structure_type::accept(type_visitor& visitor) const { visitor.visit(*this); diff --git a/src/bin/lttng-sessiond/field.hpp b/src/bin/lttng-sessiond/field.hpp index a774d133a..2e3a67bf4 100644 --- a/src/bin/lttng-sessiond/field.hpp +++ b/src/bin/lttng-sessiond/field.hpp @@ -9,6 +9,7 @@ #define LTTNG_FIELD_H #include +#include #include @@ -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& lhs, } } /* namespace details */ -template +template class typed_enumeration_type : public enumeration_type { public: using mapping = details::enumeration_mapping; @@ -264,6 +273,12 @@ public: { } + virtual type::cuptr copy() const override + { + return lttng::make_unique>( + alignment, byte_order, size, base_, mappings_, roles_); + } + virtual void accept(type_visitor& visitor) const override final; const std::shared_ptr 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 +template class variant_type : public type { static_assert(std::is_samecopy()); + } + + return lttng::make_unique>( + 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 : formatter +::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 + : formatter { + template + 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 + : formatter { + template + 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 */ diff --git a/src/bin/lttng-sessiond/tsdl-trace-class-visitor.cpp b/src/bin/lttng-sessiond/tsdl-trace-class-visitor.cpp index d240e1fb0..ac1be1bf2 100644 --- a/src/bin/lttng-sessiond/tsdl-trace-class-visitor.cpp +++ b/src/bin/lttng-sessiond/tsdl-trace-class-visitor.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -21,6 +22,7 @@ #include #include #include +#include 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 safe_tsdl_identifiers = { +const std::unordered_set 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; + + 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; + + 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 + lst::type::cuptr _create_sanitized_selector( + const lst::typed_enumeration_type& original_selector, + const unsafe_names& unsafe_names_found) + { + auto new_mappings = std::make_shared::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>( + original_selector.alignment, original_selector.byte_order, + original_selector.size, original_selector.base_, new_mappings); + } + + template + const typename lst::typed_enumeration_type::mapping& + _find_enumeration_mapping_by_range( + const typename lst::typed_enumeration_type& + 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 + lst::type::cuptr _create_sanitized_variant( + const lst::variant_type& original_variant) + { + typename lst::variant_type::choices new_choices; + const auto& sanitized_selector = static_cast< + const lst::typed_enumeration_type&>( + _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>( + original_variant.alignment, + original_variant.selector_field_location, + std::move(new_choices)); + } + + template + void visit_variant(const lst::variant_type& type) + { + unsafe_names unsafe_names_found; + static const std::unordered_set 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&>( + 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&>(type)); + _type_overrides.publish(type, std::move(sanitized_variant_type)); + } + + virtual void visit(const lst::variant_type& type) override final + { + visit_variant(type); + } + + virtual void visit(const lst::variant_type& 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& 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(type)); - _description += integer_visitor.transfer_description() + " {\n"; + visit(static_cast(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(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(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(); + } +} diff --git a/src/bin/lttng-sessiond/tsdl-trace-class-visitor.hpp b/src/bin/lttng-sessiond/tsdl-trace-class-visitor.hpp index d06ea9983..1b8bd642d 100644 --- a/src/bin/lttng-sessiond/tsdl-trace-class-visitor.hpp +++ b/src/bin/lttng-sessiond/tsdl-trace-class-visitor.hpp @@ -15,6 +15,7 @@ #include #include +#include namespace lttng { namespace sessiond { @@ -22,6 +23,36 @@ namespace tsdl { using append_metadata_fragment_function = std::function; +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 + _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 */ diff --git a/src/bin/lttng-sessiond/ust-app.cpp b/src/bin/lttng-sessiond/ust-app.cpp index 8addb5eda..11d604ea9 100644 --- a/src/bin/lttng-sessiond/ust-app.cpp +++ b/src/bin/lttng-sessiond/ust-app.cpp @@ -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 diff --git a/src/bin/lttng-sessiond/ust-app.hpp b/src/bin/lttng-sessiond/ust-app.hpp index c48e2b9e2..d0cbd217c 100644 --- a/src/bin/lttng-sessiond/ust-app.hpp +++ b/src/bin/lttng-sessiond/ust-app.hpp @@ -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; }; /* diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 165b7c3a1..39c531cba 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -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 \ diff --git a/src/common/exception.cpp b/src/common/exception.cpp index f1c530f7e..5645d7d91 100644 --- a/src/common/exception.cpp +++ b/src/common/exception.cpp @@ -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, diff --git a/src/common/exception.hpp b/src/common/exception.hpp index b7e8261ed..30a403470 100644 --- a/src/common/exception.hpp +++ b/src/common/exception.hpp @@ -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 index 000000000..46479bc57 --- /dev/null +++ b/src/common/scope-exit.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 Jérémie Galarneau + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_SCOPE_EXIT_H +#define LTTNG_SCOPE_EXIT_H + +#include + +namespace lttng { + +namespace details { +/* Is operator() of InvocableType is marked as noexcept? */ +template +struct is_invocation_noexcept + : std::integral_constant())())> { +}; +} /* 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 +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::value, + "scope_exit requires a noexcept invocable type"); + + explicit scope_exit(ScopeExitInvocableType&& scope_exit_callable) : + _on_scope_exit{std::forward(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 +scope_exit make_scope_exit(ScopeExitInvocableType&& scope_exit_callable) +{ + return scope_exit( + std::forward(scope_exit_callable)); +} + +} /* namespace lttng */ + +#endif /* LTTNG_SCOPE_EXIT_H */