sessiond: ust: conditionally enable the underscore prefix variant quirk
[lttng-tools.git] / src / bin / lttng-sessiond / tsdl-trace-class-visitor.cpp
1 /*
2 * Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 */
7
8 #include "tsdl-trace-class-visitor.hpp"
9 #include "clock-class.hpp"
10
11 #include <common/exception.hpp>
12 #include <common/format.hpp>
13 #include <common/make-unique.hpp>
14 #include <common/uuid.hpp>
15 #include <common/scope-exit.hpp>
16
17 #include <vendor/optional.hpp>
18
19 #include <algorithm>
20 #include <array>
21 #include <locale>
22 #include <queue>
23 #include <set>
24 #include <stack>
25 #include <unordered_set>
26
27 namespace lst = lttng::sessiond::trace;
28 namespace tsdl = lttng::sessiond::tsdl;
29
30 namespace {
31 const auto ctf_spec_major = 1;
32 const auto ctf_spec_minor = 8;
33
34 /*
35 * Although the CTF v1.8 specification recommends ignoring any leading underscore, Some readers,
36 * such as Babeltrace 1.x, expect special identifiers without a prepended underscore.
37 */
38 const std::unordered_set<std::string> safe_tsdl_identifiers = {
39 "stream_id",
40 "packet_size",
41 "content_size",
42 "id",
43 "v",
44 "timestamp",
45 "events_discarded",
46 "packet_seq_num",
47 "timestamp_begin",
48 "timestamp_end",
49 "cpu_id",
50 "magic",
51 "uuid",
52 "stream_instance_id"
53 };
54
55 /*
56 * A previous implementation always prepended '_' to the identifiers in order to
57 * side-step the problem of escaping TSDL keywords and ensuring identifiers
58 * started with an alphabetic character.
59 *
60 * Changing this behaviour to a smarter algorithm would break readers that have
61 * come to expect this initial underscore.
62 */
63 std::string escape_tsdl_identifier(const std::string& original_identifier)
64 {
65 if (original_identifier.size() == 0) {
66 LTTNG_THROW_ERROR("Invalid 0-length identifier used in trace description");
67 }
68
69 if (safe_tsdl_identifiers.find(original_identifier) != safe_tsdl_identifiers.end()) {
70 return original_identifier;
71 }
72
73 std::string new_identifier;
74 /* Optimisticly assume most identifiers are valid and allocate the same length. */
75 new_identifier.reserve(original_identifier.size());
76 new_identifier = "_";
77
78 /* Replace illegal characters by '_'. */
79 std::locale c_locale{"C"};
80 for (const auto current_char : original_identifier) {
81 if (!std::isalnum(current_char, c_locale) && current_char != '_') {
82 new_identifier += '_';
83 } else {
84 new_identifier += current_char;
85 }
86 }
87
88 return new_identifier;
89 }
90
91 std::string escape_tsdl_env_string_value(const std::string& original_string)
92 {
93 std::string escaped_string;
94
95 escaped_string.reserve(original_string.size());
96
97 for (const auto c : original_string) {
98 switch (c) {
99 case '\n':
100 escaped_string += "\\n";
101 break;
102 case '\\':
103 escaped_string += "\\\\";
104 break;
105 case '"':
106 escaped_string += "\"";
107 break;
108 default:
109 escaped_string += c;
110 break;
111 }
112 }
113
114 return escaped_string;
115 }
116
117 /*
118 * Variants produced by LTTng-UST contain TSDL-unsafe names. A variant/selector
119 * sanitization pass is performed before serializing a trace class hierarchy to
120 * TSDL.
121 *
122 * The variant_tsdl_keyword_sanitizer visitor is used to visit field before it
123 * is handed-over to the actual TSDL-producing visitor.
124 *
125 * As it visits fields, the variant_tsdl_keyword_sanitizer populates a
126 * "type_overrider" with TSDL-safe replacements for any variant or enumeration
127 * that uses TSDL-unsafe identifiers (reserved keywords).
128 *
129 * The type_overrider, in turn, is used by the rest of the TSDL serialization
130 * visitor (tsdl_field_visitor) to swap any TSDL-unsafe types with their
131 * sanitized version.
132 *
133 * The tsdl_field_visitor owns the type_overrider and only briefly shares it
134 * with the variant_tsdl_keyword_sanitizer which takes a reference to it.
135 */
136 class variant_tsdl_keyword_sanitizer : public lttng::sessiond::trace::field_visitor,
137 public lttng::sessiond::trace::type_visitor {
138 public:
139 using type_lookup_function = std::function<const lst::type&(const lst::field_location&)>;
140
141 variant_tsdl_keyword_sanitizer(tsdl::details::type_overrider& type_overrides,
142 type_lookup_function lookup_type) :
143 _type_overrides{type_overrides}, _lookup_type(lookup_type)
144 {
145 }
146
147 private:
148 class _c_string_comparator {
149 public:
150 int operator()(const char *lhs, const char *rhs) const
151 {
152 return std::strcmp(lhs, rhs) < 0;
153 }
154 };
155 using unsafe_names = std::set<const char *, _c_string_comparator>;
156
157 virtual void visit(const lst::field& field) override final
158 {
159 _type_overrides.type(field.get_type()).accept(*this);
160 }
161
162 virtual void visit(const lst::integer_type& type __attribute__((unused))) override final
163 {
164 }
165
166 virtual void visit(const lst::floating_point_type& type __attribute__((unused))) override final
167 {
168 }
169
170 virtual void visit(const lst::signed_enumeration_type& type __attribute__((unused))) override final
171 {
172 }
173
174 virtual void visit(const lst::unsigned_enumeration_type& type __attribute__((unused))) override final
175 {
176 }
177
178 virtual void visit(const lst::static_length_array_type& type __attribute__((unused))) override final
179 {
180 }
181
182 virtual void visit(const lst::dynamic_length_array_type& type __attribute__((unused))) override final
183 {
184 }
185
186 virtual void visit(const lst::static_length_blob_type& type __attribute__((unused))) override final
187 {
188 }
189
190 virtual void visit(const lst::dynamic_length_blob_type& type __attribute__((unused))) override final
191 {
192 }
193
194 virtual void visit(const lst::null_terminated_string_type& type __attribute__((unused))) override final
195 {
196 }
197
198 virtual void visit(const lst::structure_type& type) override final
199 {
200 /* Recurse into structure attributes. */
201 for (const auto& field : type.fields_) {
202 field->accept(*this);
203 }
204 }
205
206 /*
207 * Create a new enumeration type replacing any mapping that match, by name, the elements in `unsafe_names_found`
208 * with a TSDL-safe version. Currently, unsafe identifiers are made safe by adding
209 * a leading underscore.
210 */
211 template <typename MappingIntegerType>
212 lst::type::cuptr _create_sanitized_selector(
213 const lst::typed_enumeration_type<MappingIntegerType>& original_selector,
214 const unsafe_names& unsafe_names_found)
215 {
216 auto new_mappings = std::make_shared<typename lst::typed_enumeration_type<
217 MappingIntegerType>::mappings>();
218
219 for (const auto& mapping : *original_selector.mappings_) {
220 if (unsafe_names_found.find(mapping.name.c_str()) ==
221 unsafe_names_found.end()) {
222 /* Mapping is safe, simply copy it. */
223 new_mappings->emplace_back(mapping);
224 } else {
225 /* Unsafe mapping, rename it and keep the rest of its attributes. */
226 new_mappings->emplace_back(
227 fmt::format("_{}", mapping.name), mapping.range);
228 }
229 }
230
231 return lttng::make_unique<lst::typed_enumeration_type<MappingIntegerType>>(
232 original_selector.alignment, original_selector.byte_order,
233 original_selector.size, original_selector.base_, new_mappings);
234 }
235
236 template <typename MappingIntegerType>
237 const typename lst::typed_enumeration_type<MappingIntegerType>::mapping&
238 _find_enumeration_mapping_by_range(
239 const typename lst::typed_enumeration_type<MappingIntegerType>&
240 enumeration_type,
241 const typename lst::typed_enumeration_type<
242 MappingIntegerType>::mapping::range_t& target_mapping_range)
243 {
244 for (const auto& mapping : *enumeration_type.mappings_) {
245 if (mapping.range == target_mapping_range) {
246 return mapping;
247 }
248 }
249
250 LTTNG_THROW_ERROR(fmt::format(
251 "Failed to find mapping by range in enumeration while sanitizing a variant: target_mapping_range={}",
252 target_mapping_range));
253 }
254
255 /*
256 * Copy `original_variant`, but use the mappings of a previously-published sanitized tag
257 * to produce a TSDL-safe version of the variant.
258 */
259 template <typename MappingIntegerType>
260 lst::type::cuptr _create_sanitized_variant(
261 const lst::variant_type<MappingIntegerType>& original_variant)
262 {
263 typename lst::variant_type<MappingIntegerType>::choices new_choices;
264 const auto& sanitized_selector = static_cast<
265 const lst::typed_enumeration_type<MappingIntegerType>&>(
266 _type_overrides.type(_lookup_type(
267 original_variant.selector_field_location)));
268
269 /* Visit variant choices to sanitize them as needed. */
270 for (const auto& choice : original_variant.choices_) {
271 choice.second->accept(*this);
272 }
273
274 for (const auto& choice : original_variant.choices_) {
275 const auto& sanitized_choice_type = _type_overrides.type(*choice.second);
276
277 new_choices.emplace_back(
278 _find_enumeration_mapping_by_range(
279 sanitized_selector, choice.first.range),
280 sanitized_choice_type.copy());
281 }
282
283 return lttng::make_unique<lst::variant_type<MappingIntegerType>>(
284 original_variant.alignment,
285 original_variant.selector_field_location,
286 std::move(new_choices));
287 }
288
289 template <typename MappingIntegerType>
290 void visit_variant(const lst::variant_type<MappingIntegerType>& type)
291 {
292 unsafe_names unsafe_names_found;
293 static const std::unordered_set<std::string> tsdl_protected_keywords = {
294 "align",
295 "callsite",
296 "const",
297 "char",
298 "clock",
299 "double",
300 "enum",
301 "env",
302 "event",
303 "floating_point",
304 "float",
305 "integer",
306 "int",
307 "long",
308 "short",
309 "signed",
310 "stream",
311 "string",
312 "struct",
313 "trace",
314 "typealias",
315 "typedef",
316 "unsigned",
317 "variant",
318 "void",
319 "_Bool",
320 "_Complex",
321 "_Imaginary",
322 };
323
324 for (const auto& choice : type.choices_) {
325 if (tsdl_protected_keywords.find(choice.first.name) != tsdl_protected_keywords.cend()) {
326 /* Choice name is illegal, we have to rename it and its matching mapping. */
327 unsafe_names_found.insert(choice.first.name.c_str());
328 }
329 }
330
331 if (unsafe_names_found.empty()) {
332 return;
333 }
334
335 /*
336 * Look-up selector field type.
337 *
338 * Since it may have been overriden previously, keep the original and overriden
339 * selector field types (which may be the same, if the original was not overriden).
340 *
341 * We work from the "overriden" selector field type to preserve any existing
342 * modifications. However, the original field type will be used to publish the new
343 * version of the type leaving only the most recent overriden type in the type
344 * overrides.
345 */
346 const auto& original_selector_type = _lookup_type(type.selector_field_location);
347 const auto& overriden_selector_type = _type_overrides.type(original_selector_type);
348
349 auto sanitized_selector_type = _create_sanitized_selector(
350 static_cast<const lst::typed_enumeration_type<MappingIntegerType>&>(
351 overriden_selector_type), unsafe_names_found);
352 _type_overrides.publish(original_selector_type, std::move(sanitized_selector_type));
353
354 auto sanitized_variant_type = _create_sanitized_variant(
355 static_cast<const lst::variant_type<MappingIntegerType>&>(type));
356 _type_overrides.publish(type, std::move(sanitized_variant_type));
357 }
358
359 virtual void visit(const lst::variant_type<lst::signed_enumeration_type::mapping::range_t::range_integer_t>& type) override final
360 {
361 visit_variant(type);
362 }
363
364 virtual void visit(const lst::variant_type<lst::unsigned_enumeration_type::mapping::range_t::range_integer_t>& type) override final
365 {
366 visit_variant(type);
367 }
368
369 virtual void visit(const lst::static_length_string_type& type __attribute__((unused))) override final
370 {
371 }
372
373 virtual void visit(const lst::dynamic_length_string_type& type __attribute__((unused))) override final
374 {
375 }
376
377 tsdl::details::type_overrider& _type_overrides;
378 const type_lookup_function _lookup_type;
379 };
380
381 class tsdl_field_visitor : public lttng::sessiond::trace::field_visitor,
382 public lttng::sessiond::trace::type_visitor {
383 public:
384 tsdl_field_visitor(const lst::abi& abi,
385 unsigned int indentation_level,
386 const tsdl::details::type_overrider& type_overrides,
387 const nonstd::optional<std::string>& in_default_clock_class_name =
388 nonstd::nullopt) :
389 _indentation_level{indentation_level},
390 _trace_abi{abi},
391 _bypass_identifier_escape{false},
392 _default_clock_class_name{in_default_clock_class_name ?
393 in_default_clock_class_name->c_str() :
394 nullptr},
395 _type_overrides{type_overrides}
396 {
397 }
398
399 /* Only call once. */
400 std::string transfer_description()
401 {
402 return std::move(_description);
403 }
404
405 private:
406 virtual void visit(const lst::field& field) override final
407 {
408 /*
409 * Hack: keep the name of the field being visited since
410 * the tracers can express sequences, variants, and arrays with an alignment
411 * constraint, which is not expressible in TSDL. To work around this limitation, an
412 * empty structure declaration is inserted when needed to express the aligment
413 * constraint. The name of this structure is generated using the field's name.
414 */
415 _current_field_name.push(_bypass_identifier_escape ?
416 field.name : escape_tsdl_identifier(field.name));
417 _type_overrides.type(field.get_type()).accept(*this);
418 _description += " ";
419 _description += _current_field_name.top();
420 _current_field_name.pop();
421
422 /*
423 * Some types requires suffixes to be appended (e.g. the length of arrays
424 * and sequences, the mappings of enumerations).
425 */
426 while (!_type_suffixes.empty()) {
427 _description += _type_suffixes.front();
428 _type_suffixes.pop();
429 }
430
431 _description += ";";
432 }
433
434 virtual void visit(const lst::integer_type& type) override final
435 {
436 _description += "integer { ";
437
438 /* Mandatory properties (no defaults). */
439 _description += fmt::format("size = {size}; align = {alignment};",
440 fmt::arg("size", type.size),
441 fmt::arg("alignment", type.alignment));
442
443 /* Defaults to unsigned. */
444 if (type.signedness_ == lst::integer_type::signedness::SIGNED) {
445 _description += " signed = true;";
446 }
447
448 /* Defaults to 10. */
449 if (type.base_ != lst::integer_type::base::DECIMAL) {
450 unsigned int base;
451
452 switch (type.base_) {
453 case lst::integer_type::base::BINARY:
454 base = 2;
455 break;
456 case lst::integer_type::base::OCTAL:
457 base = 8;
458 break;
459 case lst::integer_type::base::HEXADECIMAL:
460 base = 16;
461 break;
462 default:
463 LTTNG_THROW_ERROR(fmt::format(
464 "Unexpected base encountered while serializing integer type to TSDL: base = {}",
465 (int) type.base_));
466 }
467
468 _description += fmt::format(" base = {};", base);
469 }
470
471 /* Defaults to the trace's native byte order. */
472 if (type.byte_order != _trace_abi.byte_order) {
473 const auto byte_order_str = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le";
474
475 _description += fmt::format(" byte_order = {};", byte_order_str);
476 }
477
478 if (_current_integer_encoding_override) {
479 const char *encoding_str;
480
481 switch (*_current_integer_encoding_override) {
482 case lst::string_type::encoding::ASCII:
483 encoding_str = "ASCII";
484 break;
485 case lst::string_type::encoding::UTF8:
486 encoding_str = "UTF8";
487 break;
488 default:
489 LTTNG_THROW_ERROR(fmt::format(
490 "Unexpected encoding encountered while serializing integer type to TSDL: encoding = {}",
491 (int) *_current_integer_encoding_override));
492 }
493
494 _description += fmt::format(" encoding = {};", encoding_str);
495 _current_integer_encoding_override.reset();
496 }
497
498 if (std::find(type.roles_.begin(), type.roles_.end(),
499 lst::integer_type::role::DEFAULT_CLOCK_TIMESTAMP) !=
500 type.roles_.end() ||
501 std::find(type.roles_.begin(), type.roles_.end(),
502 lst::integer_type::role::
503 PACKET_END_DEFAULT_CLOCK_TIMESTAMP) !=
504 type.roles_.end()) {
505 LTTNG_ASSERT(_default_clock_class_name);
506 _description += fmt::format(
507 " map = clock.{}.value;", _default_clock_class_name);
508 }
509
510 _description += " }";
511 }
512
513 virtual void visit(const lst::floating_point_type& type) override final
514 {
515 _description += fmt::format(
516 "floating_point {{ align = {alignment}; mant_dig = {mantissa_digits}; exp_dig = {exponent_digits};",
517 fmt::arg("alignment", type.alignment),
518 fmt::arg("mantissa_digits", type.mantissa_digits),
519 fmt::arg("exponent_digits", type.exponent_digits));
520
521 /* Defaults to the trace's native byte order. */
522 if (type.byte_order != _trace_abi.byte_order) {
523 const auto byte_order_str = type.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le";
524
525 _description += fmt::format(" byte_order = {};", byte_order_str);
526 }
527
528 _description += " }";
529 }
530
531 template <class EnumerationType>
532 void visit_enumeration(const EnumerationType& type)
533 {
534 /* name follows, when applicable. */
535 _description += "enum : ";
536
537 visit(static_cast<const lst::integer_type&>(type));
538 _description += " {\n";
539
540 const auto mappings_indentation_level = _indentation_level + 1;
541
542 bool first_mapping = true;
543 for (const auto& mapping : *type.mappings_) {
544 if (!first_mapping) {
545 _description += ",\n";
546 }
547
548 _description.resize(_description.size() + mappings_indentation_level, '\t');
549 if (mapping.range.begin == mapping.range.end) {
550 _description += fmt::format(
551 "\"{mapping_name}\" = {mapping_value}",
552 fmt::arg("mapping_name", mapping.name),
553 fmt::arg("mapping_value", mapping.range.begin));
554 } else {
555 _description += fmt::format(
556 "\"{mapping_name}\" = {mapping_range_begin} ... {mapping_range_end}",
557 fmt::arg("mapping_name", mapping.name),
558 fmt::arg("mapping_range_begin",
559 mapping.range.begin),
560 fmt::arg("mapping_range_end", mapping.range.end));
561 }
562
563 first_mapping = false;
564 }
565
566 _description += "\n";
567 _description.resize(_description.size() + _indentation_level, '\t');
568 _description += "}";
569 }
570
571 virtual void visit(const lst::signed_enumeration_type& type) override final
572 {
573 visit_enumeration(type);
574 }
575
576 virtual void visit(const lst::unsigned_enumeration_type& type) override final
577 {
578 visit_enumeration(type);
579 }
580
581 virtual void visit(const lst::static_length_array_type& type) override final
582 {
583 if (type.alignment != 0) {
584 LTTNG_ASSERT(_current_field_name.size() > 0);
585 _description += fmt::format(
586 "struct {{ }} align({alignment}) {field_name}_padding;\n",
587 fmt::arg("alignment", type.alignment),
588 fmt::arg("field_name", _current_field_name.top()));
589 _description.resize(_description.size() + _indentation_level, '\t');
590 }
591
592 type.element_type->accept(*this);
593 _type_suffixes.emplace(fmt::format("[{}]", type.length));
594 }
595
596 virtual void visit(const lst::dynamic_length_array_type& type) override final
597 {
598 if (type.alignment != 0) {
599 /*
600 * Note that this doesn't support nested sequences. For
601 * the moment, tracers can't express those. However, we
602 * could wrap nested sequences in structures, which
603 * would allow us to express alignment constraints.
604 */
605 LTTNG_ASSERT(_current_field_name.size() > 0);
606 _description += fmt::format(
607 "struct {{ }} align({alignment}) {field_name}_padding;\n",
608 fmt::arg("alignment", type.alignment),
609 fmt::arg("field_name", _current_field_name.top()));
610 _description.resize(_description.size() + _indentation_level, '\t');
611 }
612
613 type.element_type->accept(*this);
614 _type_suffixes.emplace(fmt::format("[{}]",
615 _bypass_identifier_escape ?
616 *(type.length_field_location.elements_.end() - 1) :
617 escape_tsdl_identifier(*(type.length_field_location.elements_.end() - 1))));
618 }
619
620 virtual void visit(const lst::static_length_blob_type& type) override final
621 {
622 /* This type doesn't exist in CTF 1.x, express it as a static length array of uint8_t. */
623 std::unique_ptr<const lst::type> uint8_element = lttng::make_unique<lst::integer_type>(8,
624 _trace_abi.byte_order, 8, lst::integer_type::signedness::UNSIGNED,
625 lst::integer_type::base::HEXADECIMAL);
626 const auto array = lttng::make_unique<lst::static_length_array_type>(
627 type.alignment, std::move(uint8_element), type.length_bytes);
628
629 visit(*array);
630 }
631
632 virtual void visit(const lst::dynamic_length_blob_type& type) override final
633 {
634 /* This type doesn't exist in CTF 1.x, express it as a dynamic length array of uint8_t. */
635 std::unique_ptr<const lst::type> uint8_element = lttng::make_unique<lst::integer_type>(0,
636 _trace_abi.byte_order, 8, lst::integer_type::signedness::UNSIGNED,
637 lst::integer_type::base::HEXADECIMAL);
638 const auto array = lttng::make_unique<lst::dynamic_length_array_type>(
639 type.alignment, std::move(uint8_element), type.length_field_location);
640
641 visit(*array);
642 }
643
644 virtual void visit(const lst::null_terminated_string_type& type) override final
645 {
646 /* Defaults to UTF-8. */
647 if (type.encoding_ == lst::null_terminated_string_type::encoding::ASCII) {
648 _description += "string { encoding = ASCII }";
649 } else {
650 _description += "string";
651 }
652 }
653
654 virtual void visit(const lst::structure_type& type) override final
655 {
656 _indentation_level++;
657 _description += "struct {";
658
659 const auto previous_bypass_identifier_escape = _bypass_identifier_escape;
660 _bypass_identifier_escape = false;
661 for (const auto& field : type.fields_) {
662 _description += "\n";
663 _description.resize(_description.size() + _indentation_level, '\t');
664 field->accept(*this);
665 }
666
667 _bypass_identifier_escape = previous_bypass_identifier_escape;
668
669 _indentation_level--;
670 if (type.fields_.size() != 0) {
671 _description += "\n";
672 _description.resize(_description.size() + _indentation_level, '\t');
673 }
674
675 _description += "}";
676 }
677
678 template <class MappingIntegerType>
679 void visit_variant(const lst::variant_type<MappingIntegerType>& type)
680 {
681 if (type.alignment != 0) {
682 LTTNG_ASSERT(_current_field_name.size() > 0);
683 _description += fmt::format(
684 "struct {{ }} align({alignment}) {field_name}_padding;\n",
685 fmt::arg("alignment", type.alignment),
686 fmt::arg("field_name", _current_field_name.top()));
687 _description.resize(_description.size() + _indentation_level, '\t');
688 }
689
690 _indentation_level++;
691 _description += fmt::format("variant <{}> {{\n",
692 _bypass_identifier_escape ?
693 *(type.selector_field_location.elements_.end() - 1) :
694 escape_tsdl_identifier(*(type.selector_field_location.elements_.end() - 1)));
695
696 /*
697 * The CTF 1.8 specification only recommends that implementations ignore
698 * leading underscores in field names. Both babeltrace 1 and 2 expect the
699 * variant choice and enumeration mapping name to match perfectly. Given that we
700 * don't have access to the tag in this context, we have to assume they match.
701 */
702 const auto previous_bypass_identifier_escape = _bypass_identifier_escape;
703 _bypass_identifier_escape = true;
704 for (const auto& field : type.choices_) {
705 _description.resize(_description.size() + _indentation_level, '\t');
706 field.second->accept(*this);
707 _description += fmt::format(" {};\n", field.first.name);
708 }
709
710 _bypass_identifier_escape = previous_bypass_identifier_escape;
711
712 _indentation_level--;
713 _description.resize(_description.size() + _indentation_level, '\t');
714 _description += "}";
715 }
716
717 virtual void visit(const lst::variant_type<lst::signed_enumeration_type::mapping::range_t::range_integer_t>& type) override final
718 {
719 visit_variant(type);
720 }
721
722 virtual void visit(const lst::variant_type<lst::unsigned_enumeration_type::mapping::range_t::range_integer_t>& type) override final
723 {
724 visit_variant(type);
725 }
726
727 lst::type::cuptr create_character_type(enum lst::string_type::encoding encoding)
728 {
729 _current_integer_encoding_override = encoding;
730 return lttng::make_unique<lst::integer_type>(8, _trace_abi.byte_order, 8,
731 lst::integer_type::signedness::UNSIGNED,
732 lst::integer_type::base::DECIMAL);
733 }
734
735 virtual void visit(const lst::static_length_string_type& type) override final
736 {
737 /*
738 * TSDL expresses static-length strings as arrays of 8-bit integer with
739 * an encoding specified.
740 */
741 const auto char_array = lttng::make_unique<lst::static_length_array_type>(
742 type.alignment, create_character_type(type.encoding_), type.length);
743
744 visit(*char_array);
745 }
746
747 virtual void visit(const lst::dynamic_length_string_type& type) override final
748 {
749 /*
750 * TSDL expresses dynamic-length strings as arrays of 8-bit integer with
751 * an encoding specified.
752 */
753 const auto char_sequence = lttng::make_unique<lst::dynamic_length_array_type>(
754 type.alignment, create_character_type(type.encoding_),
755 type.length_field_location);
756
757 visit(*char_sequence);
758 }
759
760 std::stack<std::string> _current_field_name;
761 /*
762 * Encoding to specify for the next serialized integer type.
763 * Since the integer_type does not allow an encoding to be specified (it is a TSDL-specific
764 * concept), this attribute is used when expressing static or dynamic length strings as
765 * arrays/sequences of bytes with an encoding.
766 */
767 nonstd::optional<enum lst::string_type::encoding> _current_integer_encoding_override;
768
769 unsigned int _indentation_level;
770 const lst::abi& _trace_abi;
771
772 std::queue<std::string> _type_suffixes;
773
774 /* Description in TSDL format. */
775 std::string _description;
776
777 bool _bypass_identifier_escape;
778 const char *_default_clock_class_name;
779 const tsdl::details::type_overrider& _type_overrides;
780 };
781
782 class tsdl_trace_environment_visitor : public lst::trace_class_environment_visitor {
783 public:
784 tsdl_trace_environment_visitor() : _environment{"env {\n"}
785 {
786 }
787
788 virtual void visit(const lst::environment_field<int64_t>& field) override
789 {
790 _environment += fmt::format(" {} = {};\n", field.name, field.value);
791 }
792
793 virtual void visit(const lst::environment_field<const char *>& field) override
794 {
795 _environment += fmt::format(" {} = \"{}\";\n", field.name,
796 escape_tsdl_env_string_value(field.value));
797 }
798
799 /* Only call once. */
800 std::string transfer_description()
801 {
802 _environment += "};\n\n";
803 return std::move(_environment);
804 }
805
806 private:
807 std::string _environment;
808 };
809 } /* namespace */
810
811 tsdl::trace_class_visitor::trace_class_visitor(const lst::abi& trace_abi,
812 tsdl::append_metadata_fragment_function append_metadata_fragment) :
813 _trace_abi{trace_abi},
814 _append_metadata_fragment(append_metadata_fragment)
815 {
816 }
817
818 void tsdl::trace_class_visitor::append_metadata_fragment(const std::string& fragment) const
819 {
820 _append_metadata_fragment(fragment);
821 }
822
823 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::trace_class& trace_class)
824 {
825 /* Ensure this instance is not used against multiple trace classes. */
826 LTTNG_ASSERT(!_current_trace_class || _current_trace_class == &trace_class);
827 _current_trace_class = &trace_class;
828
829 tsdl_field_visitor packet_header_visitor{trace_class.abi, 1, _sanitized_types_overrides};
830
831 trace_class.get_packet_header()->accept(packet_header_visitor);
832
833 /* Declare type aliases, trace class, and packet header. */
834 auto trace_class_tsdl = fmt::format(
835 "/* CTF {ctf_major}.{ctf_minor} */\n\n"
836 "trace {{\n"
837 " major = {ctf_major};\n"
838 " minor = {ctf_minor};\n"
839 " uuid = \"{uuid}\";\n"
840 " byte_order = {byte_order};\n"
841 " packet.header := {packet_header_layout};\n"
842 "}};\n\n",
843 fmt::arg("ctf_major", ctf_spec_major),
844 fmt::arg("ctf_minor", ctf_spec_minor),
845 fmt::arg("uint8_t_alignment", trace_class.abi.uint8_t_alignment),
846 fmt::arg("uint16_t_alignment", trace_class.abi.uint16_t_alignment),
847 fmt::arg("uint32_t_alignment", trace_class.abi.uint32_t_alignment),
848 fmt::arg("uint64_t_alignment", trace_class.abi.uint64_t_alignment),
849 fmt::arg("long_alignment", trace_class.abi.long_alignment),
850 fmt::arg("long_size", trace_class.abi.long_alignment),
851 fmt::arg("bits_per_long", trace_class.abi.bits_per_long),
852 fmt::arg("uuid", lttng::utils::uuid_to_str(trace_class.uuid)),
853 fmt::arg("byte_order",
854 trace_class.abi.byte_order == lst::byte_order::BIG_ENDIAN_ ? "be" : "le"),
855 fmt::arg("packet_header_layout", packet_header_visitor.transfer_description()));
856
857 /* Declare trace scope and type aliases. */
858 append_metadata_fragment(trace_class_tsdl);
859
860 tsdl_trace_environment_visitor environment_visitor;
861 trace_class.accept(environment_visitor);
862 append_metadata_fragment(environment_visitor.transfer_description());
863 }
864
865 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::clock_class& clock_class)
866 {
867 auto uuid_str = clock_class.uuid ?
868 fmt::format(" uuid = \"{}\";\n",
869 lttng::utils::uuid_to_str(*clock_class.uuid)) :
870 "";
871
872 /* Assumes a single clock that maps to specific stream class fields/roles. */
873 auto clock_class_str = fmt::format(
874 "clock {{\n"
875 " name = \"{name}\";\n"
876 /* Optional uuid. */
877 "{uuid}"
878 " description = \"{description}\";\n"
879 " freq = {frequency};\n"
880 " offset = {offset};\n"
881 "}};\n"
882 "\n",
883 fmt::arg("name", clock_class.name),
884 fmt::arg("uuid", uuid_str),
885 fmt::arg("description", clock_class.description),
886 fmt::arg("frequency", clock_class.frequency),
887 fmt::arg("offset", clock_class.offset));
888
889 append_metadata_fragment(clock_class_str);
890 }
891
892 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::stream_class& stream_class)
893 {
894 _current_stream_class = &stream_class;
895 const auto clear_stream_class_on_exit = lttng::make_scope_exit(
896 [this]() noexcept { _current_stream_class = nullptr; });
897
898 auto stream_class_str = fmt::format("stream {{\n"
899 " id = {};\n", stream_class.id);
900 variant_tsdl_keyword_sanitizer variant_sanitizer(_sanitized_types_overrides,
901 [this](const lttng::sessiond::trace::field_location& location)
902 -> const lst::type& {
903 return _lookup_field_type(location);
904 });
905
906 const auto *event_header = stream_class.get_event_header();
907 if (event_header) {
908 tsdl_field_visitor event_header_visitor{_trace_abi, 1, _sanitized_types_overrides,
909 stream_class.default_clock_class_name};
910
911 event_header->accept(variant_sanitizer);
912 event_header->accept(event_header_visitor);
913 stream_class_str += fmt::format(" event.header := {};\n",
914 event_header_visitor.transfer_description());
915 }
916
917 const auto *packet_context = stream_class.get_packet_context();
918 if (packet_context) {
919 tsdl_field_visitor packet_context_visitor{_trace_abi, 1, _sanitized_types_overrides,
920 stream_class.default_clock_class_name};
921
922 packet_context->accept(variant_sanitizer);
923 packet_context->accept(packet_context_visitor);
924 stream_class_str += fmt::format(" packet.context := {};\n",
925 packet_context_visitor.transfer_description());
926 }
927
928 const auto *event_context = stream_class.get_event_context();
929 if (event_context) {
930 tsdl_field_visitor event_context_visitor{_trace_abi, 1, _sanitized_types_overrides};
931
932 event_context->accept(variant_sanitizer);
933 event_context->accept(event_context_visitor);
934 stream_class_str += fmt::format(" event.context := {};\n",
935 event_context_visitor.transfer_description());
936 }
937
938 stream_class_str += "};\n\n";
939
940 append_metadata_fragment(stream_class_str);
941 }
942
943 void tsdl::trace_class_visitor::visit(const lttng::sessiond::trace::event_class& event_class)
944 {
945 _current_event_class = &event_class;
946 const auto clear_event_class_on_exit = lttng::make_scope_exit(
947 [this]() noexcept { _current_event_class = nullptr; });
948
949 auto event_class_str = fmt::format("event {{\n"
950 " name = \"{name}\";\n"
951 " id = {id};\n"
952 " stream_id = {stream_class_id};\n"
953 " loglevel = {log_level};\n",
954 fmt::arg("name", event_class.name),
955 fmt::arg("id", event_class.id),
956 fmt::arg("stream_class_id", event_class.stream_class_id),
957 fmt::arg("log_level", event_class.log_level));
958
959 if (event_class.model_emf_uri) {
960 event_class_str += fmt::format(
961 " model.emf.uri = \"{}\";\n", *event_class.model_emf_uri);
962 }
963
964 tsdl_field_visitor payload_visitor{_trace_abi, 1, _sanitized_types_overrides};
965 variant_tsdl_keyword_sanitizer variant_sanitizer(_sanitized_types_overrides,
966 [this](const lttng::sessiond::trace::field_location& location)
967 -> const lst::type& {
968 return _lookup_field_type(location);
969 });
970
971 event_class.payload->accept(variant_sanitizer);
972 event_class.payload->accept(payload_visitor);
973
974 event_class_str += fmt::format(
975 " fields := {};\n}};\n\n", payload_visitor.transfer_description());
976
977 append_metadata_fragment(event_class_str);
978 }
979
980 void tsdl::details::type_overrider::publish(
981 const lttng::sessiond::trace::type& original_type,
982 lttng::sessiond::trace::type::cuptr new_type_override)
983 {
984 auto current_override = _overriden_types.find(&original_type);
985
986 if (current_override != _overriden_types.end()) {
987 current_override->second = std::move(new_type_override);
988 } else {
989 _overriden_types.insert(std::make_pair(&original_type, std::move(new_type_override)));
990 }
991 }
992
993 const lst::type& tsdl::details::type_overrider::type(
994 const lttng::sessiond::trace::type& original) const noexcept
995 {
996 const auto result = _overriden_types.find(&original);
997
998 if (result != _overriden_types.end()) {
999 /* Provide the overriden type. */
1000 return *result->second;
1001 }
1002
1003 /* Pass the original type through. */
1004 return original;
1005 }
1006
1007 namespace {
1008 const lttng::sessiond::trace::type& lookup_type_from_root_type(
1009 const lttng::sessiond::trace::type& root_type,
1010 const lttng::sessiond::trace::field_location& field_location)
1011 {
1012 const auto *type = &root_type;
1013
1014 for (const auto& location_element : field_location.elements_) {
1015 /* Only structures can be traversed. */
1016 const auto *struct_type = dynamic_cast<const lst::structure_type *>(type);
1017
1018 /*
1019 * Traverse the type by following the field location path.
1020 *
1021 * While field paths are assumed to have been validated before-hand,
1022 * a dynamic cast is performed here as an additional precaution
1023 * since none of this is performance-critical; it can be removed
1024 * safely.
1025 */
1026 if (!struct_type) {
1027 LTTNG_THROW_ERROR(fmt::format(
1028 "Encountered a type that is not a structure while traversing field location: field-location=`{}`",
1029 field_location));
1030 }
1031
1032 const auto field_found_it = std::find_if(struct_type->fields_.cbegin(),
1033 struct_type->fields_.cend(),
1034 [&location_element](const lst::field::cuptr& struct_field) {
1035 return struct_field->name == location_element;
1036 });
1037
1038 if (field_found_it == struct_type->fields_.cend()) {
1039 LTTNG_THROW_ERROR(fmt::format(
1040 "Failed to find field using field location: field-name:=`{field_name}`, field-location=`{field_location}`",
1041 fmt::arg("field_location", field_location),
1042 fmt::arg("field_name", location_element)));
1043 }
1044
1045 type = &(*field_found_it)->get_type();
1046 }
1047
1048 return *type;
1049 }
1050 } /* anonymous namespace. */
1051
1052 /*
1053 * The trace hierarchy is assumed to have been validated on creation.
1054 * This function can only fail due to a validation error, hence
1055 * why it throws on any unexpected/invalid field location.
1056 *
1057 * Does not return an overriden field type; it returns the original field type
1058 * as found in the trace hierarchy.
1059 */
1060 const lttng::sessiond::trace::type& lttng::sessiond::tsdl::trace_class_visitor::_lookup_field_type(
1061 const lttng::sessiond::trace::field_location& location) const
1062 {
1063 /* Validate the look-up is happening in a valid visit context. */
1064 switch (location.root_) {
1065 case lst::field_location::root::EVENT_RECORD_HEADER:
1066 case lst::field_location::root::EVENT_RECORD_PAYLOAD:
1067 if (!_current_event_class) {
1068 LTTNG_THROW_ERROR(
1069 "Field type look-up failure: no current event class in visitor's context");
1070 }
1071 /* fall through. */
1072 case lst::field_location::root::EVENT_RECORD_COMMON_CONTEXT:
1073 case lst::field_location::root::PACKET_CONTEXT:
1074 if (!_current_stream_class) {
1075 LTTNG_THROW_ERROR(
1076 "Field type look-up failure: no current stream class in visitor's context");
1077 }
1078 /* fall through. */
1079 case lst::field_location::root::PACKET_HEADER:
1080 if (!_current_trace_class) {
1081 LTTNG_THROW_ERROR(
1082 "Field type look-up failure: no current trace class in visitor's context");
1083 }
1084
1085 break;
1086 case lst::field_location::root::EVENT_RECORD_SPECIFIC_CONTEXT:
1087 LTTNG_THROW_UNSUPPORTED_ERROR(
1088 "Field type look-up failure: event-record specific contexts are not supported");
1089 default:
1090 LTTNG_THROW_UNSUPPORTED_ERROR(
1091 "Field type look-up failure: unknown field location root");
1092 }
1093
1094 switch (location.root_) {
1095 case lst::field_location::root::PACKET_HEADER:
1096 return lookup_type_from_root_type(
1097 *_current_trace_class->get_packet_header(), location);
1098 case lst::field_location::root::PACKET_CONTEXT:
1099 return lookup_type_from_root_type(
1100 *_current_stream_class->get_packet_context(), location);
1101 case lst::field_location::root::EVENT_RECORD_HEADER:
1102 return lookup_type_from_root_type(
1103 *_current_stream_class->get_event_header(), location);
1104 case lst::field_location::root::EVENT_RECORD_COMMON_CONTEXT:
1105 return lookup_type_from_root_type(
1106 *_current_stream_class->get_event_context(), location);
1107 case lst::field_location::root::EVENT_RECORD_PAYLOAD:
1108 return lookup_type_from_root_type(
1109 *_current_event_class->payload, location);
1110 case lst::field_location::root::EVENT_RECORD_SPECIFIC_CONTEXT:
1111 default:
1112 /* Unreachable as it was checked before. */
1113 abort();
1114 }
1115 }
This page took 0.088084 seconds and 4 git commands to generate.