cpp-common/bt2c/logging.hpp: remove no-formatting ("str") alternatives
[babeltrace.git] / src / plugins / ctf / common / src / metadata / tsdl / ctf-meta-resolve.cpp
CommitLineData
44c440bc 1/*
0235b0db 2 * SPDX-License-Identifier: MIT
44c440bc 3 *
0235b0db
MJ
4 * Copyright 2016-2018 Philippe Proulx <pproulx@efficios.com>
5 * Copyright 2015 Jérémie Galarneau <jeremie.galarneau@efficios.com>
44c440bc
PP
6 */
7
c802cacb
SM
8#include <babeltrace2/babeltrace.h>
9
578e048b
MJ
10#include "common/assert.h"
11#include "common/common.h"
0f5c5d5c
SM
12#include "cpp-common/bt2c/logging.hpp"
13#include "cpp-common/vendor/fmt/format.h"
44c440bc 14
087cd0f5 15#include "ctf-meta-visitors.hpp"
44c440bc 16
4164020e 17using field_class_stack_t = GPtrArray;
44c440bc
PP
18
19/*
20 * A stack frame.
21 *
5cd6d0e5
PP
22 * `fc` contains a compound field class (structure, variant, array,
23 * or sequence) and `index` indicates the index of the field class in
24 * the upper frame (-1 for array and sequence field classes). `name`
25 * indicates the name of the field class in the upper frame (empty
26 * string for array and sequence field classes).
44c440bc 27 */
4164020e
SM
28struct field_class_stack_frame
29{
30 struct ctf_field_class *fc;
31 int64_t index;
44c440bc
PP
32};
33
34/*
35 * The current context of the resolving engine.
36 */
4164020e
SM
37struct resolve_context
38{
0f5c5d5c
SM
39 explicit resolve_context(const bt2c::Logger& parentLogger) :
40 logger {parentLogger, "PLUGIN/CTF/META/RESOLVE"}
41 {
42 }
4164020e 43
0f5c5d5c 44 bt2c::Logger logger;
4164020e 45
afb0f12b
SM
46 struct ctf_trace_class *tc = nullptr;
47 struct ctf_stream_class *sc = nullptr;
48 struct ctf_event_class *ec = nullptr;
4164020e
SM
49
50 struct
51 {
afb0f12b
SM
52 struct ctf_field_class *packet_header = nullptr;
53 struct ctf_field_class *packet_context = nullptr;
54 struct ctf_field_class *event_header = nullptr;
55 struct ctf_field_class *event_common_context = nullptr;
56 struct ctf_field_class *event_spec_context = nullptr;
57 struct ctf_field_class *event_payload = nullptr;
4164020e
SM
58 } scopes;
59
60 /* Root scope being visited */
afb0f12b
SM
61 enum ctf_scope root_scope = CTF_SCOPE_PACKET_HEADER;
62 field_class_stack_t *field_class_stack = nullptr;
63 struct ctf_field_class *cur_fc = nullptr;
44c440bc
PP
64};
65
66/* TSDL dynamic scope prefixes as defined in CTF Section 7.3.2 */
67static const char * const absolute_path_prefixes[] = {
4164020e
SM
68 /* CTF_SCOPE_PACKET_HEADER */ "trace.packet.header.",
69 /* CTF_SCOPE_PACKET_CONTEXT */ "stream.packet.context.",
70 /* CTF_SCOPE_EVENT_HEADER */ "stream.event.header.",
71 /* CTF_SCOPE_EVENT_COMMON_CONTEXT */ "stream.event.context.",
72 /* CTF_SCOPE_EVENT_SPECIFIC_CONTEXT */ "event.context.",
73 /* CTF_SCOPE_EVENT_PAYLOAD */ "event.fields.",
44c440bc
PP
74};
75
76/* Number of path tokens used for the absolute prefixes */
77static const uint64_t absolute_path_prefix_ptoken_counts[] = {
4164020e
SM
78 /* CTF_SCOPE_PACKET_HEADER */ 3,
79 /* CTF_SCOPE_PACKET_CONTEXT */ 3,
80 /* CTF_SCOPE_EVENT_HEADER */ 3,
81 /* CTF_SCOPE_EVENT_COMMON_CONTEXT */ 3,
82 /* CTF_SCOPE_EVENT_SPECIFIC_CONTEXT */ 2,
83 /* CTF_SCOPE_EVENT_PAYLOAD */ 2,
44c440bc
PP
84};
85
4164020e 86static void destroy_field_class_stack_frame(struct field_class_stack_frame *frame)
44c440bc 87{
4164020e
SM
88 if (!frame) {
89 return;
90 }
44c440bc 91
4164020e 92 g_free(frame);
44c440bc
PP
93}
94
95/*
5cd6d0e5 96 * Creates a class stack.
44c440bc 97 */
4164020e 98static field_class_stack_t *field_class_stack_create(void)
44c440bc 99{
4164020e 100 return g_ptr_array_new_with_free_func((GDestroyNotify) destroy_field_class_stack_frame);
44c440bc
PP
101}
102
103/*
5cd6d0e5 104 * Destroys a class stack.
44c440bc 105 */
4164020e 106static void field_class_stack_destroy(field_class_stack_t *stack)
44c440bc 107{
4164020e
SM
108 if (stack) {
109 g_ptr_array_free(stack, TRUE);
110 }
44c440bc
PP
111}
112
113/*
5cd6d0e5 114 * Pushes a field class onto a class stack.
44c440bc 115 */
4164020e
SM
116static int field_class_stack_push(field_class_stack_t *stack, struct ctf_field_class *fc,
117 struct resolve_context *ctx)
44c440bc 118{
4164020e
SM
119 int ret = 0;
120 struct field_class_stack_frame *frame = NULL;
121
122 if (!stack || !fc) {
0f5c5d5c
SM
123 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
124 "Invalid parameter: stack or field class is `NULL`.");
4164020e
SM
125 ret = -1;
126 goto end;
127 }
128
129 frame = g_new0(struct field_class_stack_frame, 1);
130 if (!frame) {
0f5c5d5c
SM
131 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
132 "Failed to allocate one field class stack frame.");
4164020e
SM
133 ret = -1;
134 goto end;
135 }
136
0f5c5d5c
SM
137 BT_CPPLOGD_SPEC(ctx->logger,
138 "Pushing field class on context's stack: "
139 "fc-addr={}, stack-size-before={}",
140 fmt::ptr(fc), stack->len);
4164020e
SM
141 frame->fc = fc;
142 g_ptr_array_add(stack, frame);
44c440bc
PP
143
144end:
4164020e 145 return ret;
44c440bc
PP
146}
147
148/*
149 * Checks whether or not `stack` is empty.
150 */
4164020e 151static bool field_class_stack_empty(field_class_stack_t *stack)
44c440bc 152{
4164020e 153 return stack->len == 0;
44c440bc
PP
154}
155
156/*
157 * Returns the number of frames in `stack`.
158 */
4164020e 159static size_t field_class_stack_size(field_class_stack_t *stack)
44c440bc 160{
4164020e 161 return stack->len;
44c440bc
PP
162}
163
164/*
165 * Returns the top frame of `stack`.
166 */
4164020e 167static struct field_class_stack_frame *field_class_stack_peek(field_class_stack_t *stack)
44c440bc 168{
4164020e
SM
169 BT_ASSERT(stack);
170 BT_ASSERT(!field_class_stack_empty(stack));
44c440bc 171
4164020e 172 return (field_class_stack_frame *) g_ptr_array_index(stack, stack->len - 1);
44c440bc
PP
173}
174
175/*
176 * Returns the frame at index `index` in `stack`.
177 */
4164020e
SM
178static struct field_class_stack_frame *field_class_stack_at(field_class_stack_t *stack,
179 size_t index)
44c440bc 180{
4164020e
SM
181 BT_ASSERT(stack);
182 BT_ASSERT(index < stack->len);
44c440bc 183
4164020e 184 return (field_class_stack_frame *) g_ptr_array_index(stack, index);
44c440bc
PP
185}
186
187/*
188 * Removes the top frame of `stack`.
189 */
4164020e 190static void field_class_stack_pop(field_class_stack_t *stack, struct resolve_context *ctx)
44c440bc 191{
4164020e
SM
192 if (!field_class_stack_empty(stack)) {
193 /*
194 * This will call the frame's destructor and free it, as
195 * well as put its contained field class.
196 */
0f5c5d5c 197 BT_CPPLOGD_SPEC(ctx->logger, "Popping context's stack: stack-size-before={}", stack->len);
4164020e
SM
198 g_ptr_array_set_size(stack, stack->len - 1);
199 }
44c440bc
PP
200}
201
202/*
5cd6d0e5 203 * Returns the scope field class of `scope` in the context `ctx`.
44c440bc 204 */
4164020e
SM
205static struct ctf_field_class *borrow_class_from_ctx(struct resolve_context *ctx,
206 enum ctf_scope scope)
44c440bc 207{
4164020e
SM
208 switch (scope) {
209 case CTF_SCOPE_PACKET_HEADER:
210 return ctx->scopes.packet_header;
211 case CTF_SCOPE_PACKET_CONTEXT:
212 return ctx->scopes.packet_context;
213 case CTF_SCOPE_EVENT_HEADER:
214 return ctx->scopes.event_header;
215 case CTF_SCOPE_EVENT_COMMON_CONTEXT:
216 return ctx->scopes.event_common_context;
217 case CTF_SCOPE_EVENT_SPECIFIC_CONTEXT:
218 return ctx->scopes.event_spec_context;
219 case CTF_SCOPE_EVENT_PAYLOAD:
220 return ctx->scopes.event_payload;
221 default:
222 bt_common_abort();
223 }
224
225 return NULL;
44c440bc
PP
226}
227
228/*
229 * Returns the CTF scope from a path string. May return -1 if the path
230 * is found to be relative.
231 */
4164020e
SM
232static enum ctf_scope get_root_scope_from_absolute_pathstr(const char *pathstr,
233 struct resolve_context *ctx)
44c440bc 234{
4164020e
SM
235 enum ctf_scope scope;
236 enum ctf_scope ret = CTF_SCOPE_PACKET_UNKNOWN;
237 const size_t prefixes_count = sizeof(absolute_path_prefixes) / sizeof(*absolute_path_prefixes);
238
239 for (scope = CTF_SCOPE_PACKET_HEADER; scope < CTF_SCOPE_PACKET_HEADER + prefixes_count;
240 scope = (ctf_scope) (scope + 1)) {
241 /*
242 * Check if path string starts with a known absolute
243 * path prefix.
244 *
245 * Refer to CTF 7.3.2 STATIC AND DYNAMIC SCOPES.
246 */
247 if (strncmp(pathstr, absolute_path_prefixes[scope],
248 strlen(absolute_path_prefixes[scope]))) {
249 /* Prefix does not match: try the next one */
0f5c5d5c
SM
250 BT_CPPLOGD_SPEC(ctx->logger,
251 "Prefix does not match: trying the next one: "
252 "path=\"{}\", path-prefix=\"{}\", scope={}",
253 pathstr, absolute_path_prefixes[scope], scope);
4164020e
SM
254 continue;
255 }
256
257 /* Found it! */
258 ret = scope;
0f5c5d5c
SM
259 BT_CPPLOGD_SPEC(ctx->logger,
260 "Found root scope from absolute path: "
261 "path=\"{}\", scope={}",
262 pathstr, scope);
4164020e
SM
263 goto end;
264 }
44c440bc
PP
265
266end:
4164020e 267 return ret;
44c440bc
PP
268}
269
270/*
271 * Destroys a path token.
272 */
ecd7492f 273static void ptokens_destroy_func(gpointer ptoken, gpointer)
44c440bc 274{
4164020e 275 g_string_free((GString *) ptoken, TRUE);
44c440bc
PP
276}
277
278/*
279 * Destroys a path token list.
280 */
4164020e 281static void ptokens_destroy(GList *ptokens)
44c440bc 282{
4164020e
SM
283 if (!ptokens) {
284 return;
285 }
44c440bc 286
4164020e
SM
287 g_list_foreach(ptokens, ptokens_destroy_func, NULL);
288 g_list_free(ptokens);
44c440bc
PP
289}
290
291/*
292 * Returns the string contained in a path token.
293 */
4164020e 294static const char *ptoken_get_string(GList *ptoken)
44c440bc 295{
4164020e 296 GString *tokenstr = (GString *) ptoken->data;
44c440bc 297
4164020e 298 return tokenstr->str;
44c440bc
PP
299}
300
301/*
302 * Converts a path string to a path token list, that is, splits the
303 * individual words of a path string into a list of individual
304 * strings.
305 */
4164020e 306static GList *pathstr_to_ptokens(const char *pathstr, struct resolve_context *ctx)
44c440bc 307{
4164020e
SM
308 const char *at = pathstr;
309 const char *last = at;
310 GList *ptokens = NULL;
44c440bc 311
4164020e
SM
312 for (;;) {
313 if (*at == '.' || *at == '\0') {
314 GString *tokenstr;
44c440bc 315
4164020e
SM
316 if (at == last) {
317 /* Error: empty token */
0f5c5d5c
SM
318 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger, "Empty path token: path=\"{}\", pos={}",
319 pathstr, (unsigned int) (at - pathstr));
4164020e
SM
320 goto error;
321 }
44c440bc 322
4164020e
SM
323 tokenstr = g_string_new(NULL);
324 g_string_append_len(tokenstr, last, at - last);
325 ptokens = g_list_append(ptokens, tokenstr);
326 last = at + 1;
327 }
44c440bc 328
4164020e
SM
329 if (*at == '\0') {
330 break;
331 }
44c440bc 332
4164020e
SM
333 at++;
334 }
44c440bc 335
4164020e 336 return ptokens;
44c440bc
PP
337
338error:
4164020e
SM
339 ptokens_destroy(ptokens);
340 return NULL;
44c440bc
PP
341}
342
343/*
344 * Converts a path token list to a field path object. The path token
5cd6d0e5 345 * list is relative from `fc`. The index of the source looking for its
45c51519
PP
346 * target within `fc` is indicated by `src_index`. This can be
347 * `INT64_MAX` if the source is contained in `fc`.
44c440bc
PP
348 *
349 * `field_path` is an output parameter owned by the caller that must be
350 * filled here.
351 */
4164020e
SM
352static int ptokens_to_field_path(GList *ptokens, struct ctf_field_path *field_path,
353 struct ctf_field_class *fc, int64_t src_index,
354 struct resolve_context *ctx)
44c440bc 355{
4164020e
SM
356 int ret = 0;
357 GList *cur_ptoken = ptokens;
358 bool first_level_done = false;
359
360 /* Locate target */
361 while (cur_ptoken) {
362 int64_t child_index;
363 struct ctf_field_class *child_fc;
364 const char *ft_name = ptoken_get_string(cur_ptoken);
365
0f5c5d5c 366 BT_CPPLOGD_SPEC(ctx->logger, "Current path token: token=\"{}\"", ft_name);
4164020e
SM
367
368 /* Find to which index corresponds the current path token */
369 if (fc->type == CTF_FIELD_CLASS_TYPE_ARRAY || fc->type == CTF_FIELD_CLASS_TYPE_SEQUENCE) {
370 child_index = -1;
371 } else {
372 child_index =
373 ctf_field_class_compound_get_field_class_index_from_orig_name(fc, ft_name);
374 if (child_index < 0) {
375 /*
376 * Error: field name does not exist or
377 * wrong current class.
378 */
0f5c5d5c
SM
379 BT_CPPLOGD_SPEC(ctx->logger,
380 "Cannot get index of field class: "
381 "field-name=\"{}\", "
382 "src-index={}, "
383 "child-index={}, "
384 "first-level-done={}",
385 ft_name, src_index, child_index, first_level_done);
4164020e
SM
386 ret = -1;
387 goto end;
388 } else if (child_index > src_index && !first_level_done) {
0f5c5d5c
SM
389 BT_CPPLOGD_SPEC(ctx->logger,
390 "Child field class is located after source field class: "
391 "field-name=\"{}\", "
392 "src-index={}, "
393 "child-index={}, "
394 "first-level-done={}",
395 ft_name, src_index, child_index, first_level_done);
4164020e
SM
396 ret = -1;
397 goto end;
398 }
399
400 /* Next path token */
401 cur_ptoken = g_list_next(cur_ptoken);
402 first_level_done = true;
403 }
404
405 /* Create new field path entry */
406 ctf_field_path_append_index(field_path, child_index);
407
408 /* Get child field class */
409 child_fc = ctf_field_class_compound_borrow_field_class_by_index(fc, child_index);
410 BT_ASSERT(child_fc);
411
412 /* Move child class to current class */
413 fc = child_fc;
414 }
44c440bc
PP
415
416end:
4164020e 417 return ret;
44c440bc
PP
418}
419
420/*
421 * Converts a known absolute path token list to a field path object
422 * within the resolving context `ctx`.
423 *
424 * `field_path` is an output parameter owned by the caller that must be
425 * filled here.
426 */
4164020e
SM
427static int absolute_ptokens_to_field_path(GList *ptokens, struct ctf_field_path *field_path,
428 struct resolve_context *ctx)
44c440bc 429{
4164020e
SM
430 int ret = 0;
431 GList *cur_ptoken;
432 struct ctf_field_class *fc;
433
434 /*
435 * Make sure we're not referring to a scope within a translated
436 * object.
437 */
438 switch (field_path->root) {
439 case CTF_SCOPE_PACKET_HEADER:
440 if (ctx->tc->is_translated) {
0f5c5d5c
SM
441 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
442 "Trace class is already translated: "
443 "root-scope={}",
444 field_path->root);
4164020e
SM
445 ret = -1;
446 goto end;
447 }
448
449 break;
450 case CTF_SCOPE_PACKET_CONTEXT:
451 case CTF_SCOPE_EVENT_HEADER:
452 case CTF_SCOPE_EVENT_COMMON_CONTEXT:
453 if (!ctx->sc) {
0f5c5d5c
SM
454 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
455 "No current stream class: "
456 "root-scope={}",
457 field_path->root);
4164020e
SM
458 ret = -1;
459 goto end;
460 }
461
462 if (ctx->sc->is_translated) {
0f5c5d5c
SM
463 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
464 "Stream class is already translated: "
465 "root-scope={}",
466 field_path->root);
4164020e
SM
467 ret = -1;
468 goto end;
469 }
470
471 break;
472 case CTF_SCOPE_EVENT_SPECIFIC_CONTEXT:
473 case CTF_SCOPE_EVENT_PAYLOAD:
474 if (!ctx->ec) {
0f5c5d5c
SM
475 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
476 "No current event class: "
477 "root-scope={}",
478 field_path->root);
4164020e
SM
479 ret = -1;
480 goto end;
481 }
482
483 if (ctx->ec->is_translated) {
0f5c5d5c
SM
484 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
485 "Event class is already translated: "
486 "root-scope={}",
487 field_path->root);
4164020e
SM
488 ret = -1;
489 goto end;
490 }
491
492 break;
493
494 default:
495 bt_common_abort();
496 }
497
498 /* Skip absolute path tokens */
499 cur_ptoken = g_list_nth(ptokens, absolute_path_prefix_ptoken_counts[field_path->root]);
500
501 /* Start with root class */
502 fc = borrow_class_from_ctx(ctx, field_path->root);
503 if (!fc) {
504 /* Error: root class is not available */
0f5c5d5c
SM
505 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
506 "Root field class is not available: "
507 "root-scope={}",
508 field_path->root);
4164020e
SM
509 ret = -1;
510 goto end;
511 }
512
513 /* Locate target */
514 ret = ptokens_to_field_path(cur_ptoken, field_path, fc, INT64_MAX, ctx);
44c440bc
PP
515
516end:
4164020e 517 return ret;
44c440bc
PP
518}
519
520/*
521 * Converts a known relative path token list to a field path object
522 * within the resolving context `ctx`.
523 *
524 * `field_path` is an output parameter owned by the caller that must be
525 * filled here.
526 */
4164020e
SM
527static int relative_ptokens_to_field_path(GList *ptokens, struct ctf_field_path *field_path,
528 struct resolve_context *ctx)
44c440bc 529{
4164020e
SM
530 int ret = 0;
531 int64_t parent_pos_in_stack;
532 struct ctf_field_path tail_field_path;
533
534 ctf_field_path_init(&tail_field_path);
535 parent_pos_in_stack = field_class_stack_size(ctx->field_class_stack) - 1;
536
537 while (parent_pos_in_stack >= 0) {
538 struct ctf_field_class *parent_class =
539 field_class_stack_at(ctx->field_class_stack, parent_pos_in_stack)->fc;
540 int64_t cur_index =
541 field_class_stack_at(ctx->field_class_stack, parent_pos_in_stack)->index;
542
0f5c5d5c
SM
543 BT_CPPLOGD_SPEC(ctx->logger,
544 "Locating target field class from current parent field class: "
545 "parent-pos={}, parent-fc-addr={}, "
546 "cur-index={}",
547 parent_pos_in_stack, fmt::ptr(parent_class), cur_index);
4164020e
SM
548
549 /* Locate target from current parent class */
550 ret = ptokens_to_field_path(ptokens, &tail_field_path, parent_class, cur_index, ctx);
551 if (ret) {
552 /* Not found... yet */
e27adb90 553 BT_CPPLOGD_SPEC(ctx->logger, "Not found at this point.");
4164020e
SM
554 ctf_field_path_clear(&tail_field_path);
555 } else {
556 /* Found: stitch tail field path to head field path */
557 uint64_t i = 0;
558 size_t tail_field_path_len = tail_field_path.path->len;
559
560 while (BT_TRUE) {
561 struct ctf_field_class *cur_class =
562 field_class_stack_at(ctx->field_class_stack, i)->fc;
563 int64_t index = field_class_stack_at(ctx->field_class_stack, i)->index;
564
565 if (cur_class == parent_class) {
566 break;
567 }
568
569 ctf_field_path_append_index(field_path, index);
570 i++;
571 }
572
573 for (i = 0; i < tail_field_path_len; i++) {
574 int64_t index = ctf_field_path_borrow_index_by_index(&tail_field_path, i);
575
576 ctf_field_path_append_index(field_path, (int64_t) index);
577 }
578 break;
579 }
580
581 parent_pos_in_stack--;
582 }
583
584 if (parent_pos_in_stack < 0) {
585 /* Not found */
586 ret = -1;
587 }
588
589 ctf_field_path_fini(&tail_field_path);
590 return ret;
44c440bc
PP
591}
592
593/*
594 * Converts a path string to a field path object within the resolving
595 * context `ctx`.
596 */
4164020e
SM
597static int pathstr_to_field_path(const char *pathstr, struct ctf_field_path *field_path,
598 struct resolve_context *ctx)
44c440bc 599{
4164020e
SM
600 int ret = 0;
601 enum ctf_scope root_scope;
602 GList *ptokens = NULL;
603
604 /* Convert path string to path tokens */
605 ptokens = pathstr_to_ptokens(pathstr, ctx);
606 if (!ptokens) {
0f5c5d5c
SM
607 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
608 "Cannot convert path string to path tokens: "
609 "path=\"{}\"",
610 pathstr);
4164020e
SM
611 ret = -1;
612 goto end;
613 }
614
615 /* Absolute or relative path? */
616 root_scope = get_root_scope_from_absolute_pathstr(pathstr, ctx);
617
618 if (root_scope == CTF_SCOPE_PACKET_UNKNOWN) {
619 /* Relative path: start with current root scope */
620 field_path->root = ctx->root_scope;
0f5c5d5c
SM
621 BT_CPPLOGD_SPEC(ctx->logger,
622 "Detected relative path: starting with current root scope: "
623 "scope={}",
624 field_path->root);
4164020e
SM
625 ret = relative_ptokens_to_field_path(ptokens, field_path, ctx);
626 if (ret) {
0f5c5d5c
SM
627 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
628 "Cannot get relative field path of path string: "
629 "path=\"{}\", start-scope={}, end-scope={}",
630 pathstr, ctx->root_scope, field_path->root);
4164020e
SM
631 goto end;
632 }
633 } else {
634 /* Absolute path: use found root scope */
635 field_path->root = root_scope;
0f5c5d5c
SM
636 BT_CPPLOGD_SPEC(ctx->logger,
637 "Detected absolute path: using root scope: "
638 "scope={}",
639 field_path->root);
4164020e
SM
640 ret = absolute_ptokens_to_field_path(ptokens, field_path, ctx);
641 if (ret) {
0f5c5d5c
SM
642 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
643 "Cannot get absolute field path of path string: "
644 "path=\"{}\", root-scope={}",
645 pathstr, root_scope);
4164020e
SM
646 goto end;
647 }
648 }
649
0f5c5d5c
SM
650 if (ret == 0) {
651 BT_CPPLOGD_SPEC(ctx->logger, "Found field path: path=\"{}\", field-path=\"{}\"", pathstr,
652 *field_path);
4164020e 653 }
44c440bc
PP
654
655end:
4164020e
SM
656 ptokens_destroy(ptokens);
657 return ret;
44c440bc
PP
658}
659
660/*
5cd6d0e5 661 * Retrieves a field class by following the field path `field_path` in
44c440bc
PP
662 * the resolving context `ctx`.
663 */
4164020e
SM
664static struct ctf_field_class *field_path_to_field_class(struct ctf_field_path *field_path,
665 struct resolve_context *ctx)
44c440bc 666{
4164020e
SM
667 uint64_t i;
668 struct ctf_field_class *fc;
669
670 /* Start with root class */
671 fc = borrow_class_from_ctx(ctx, field_path->root);
672 if (!fc) {
673 /* Error: root class is not available */
0f5c5d5c
SM
674 BT_CPPLOGE_APPEND_CAUSE_SPEC(
675 ctx->logger, "Root field class is not available: root-scope={}", field_path->root);
4164020e
SM
676 goto end;
677 }
678
679 /* Locate target */
680 for (i = 0; i < field_path->path->len; i++) {
681 struct ctf_field_class *child_fc;
682 int64_t child_index = ctf_field_path_borrow_index_by_index(field_path, i);
683
684 /* Get child field class */
685 child_fc = ctf_field_class_compound_borrow_field_class_by_index(fc, child_index);
686 BT_ASSERT(child_fc);
687
688 /* Move child class to current class */
689 fc = child_fc;
690 }
44c440bc
PP
691
692end:
4164020e 693 return fc;
44c440bc
PP
694}
695
696/*
5cd6d0e5 697 * Fills the equivalent field path object of the context class stack.
44c440bc 698 */
4164020e 699static void get_ctx_stack_field_path(struct resolve_context *ctx, struct ctf_field_path *field_path)
44c440bc 700{
4164020e 701 uint64_t i;
44c440bc 702
4164020e
SM
703 BT_ASSERT(field_path);
704 field_path->root = ctx->root_scope;
705 ctf_field_path_clear(field_path);
44c440bc 706
4164020e
SM
707 for (i = 0; i < field_class_stack_size(ctx->field_class_stack); i++) {
708 struct field_class_stack_frame *frame = field_class_stack_at(ctx->field_class_stack, i);
44c440bc 709
4164020e
SM
710 ctf_field_path_append_index(field_path, frame->index);
711 }
44c440bc
PP
712}
713
714/*
715 * Returns the index of the lowest common ancestor of two field path
716 * objects having the same root scope.
717 */
4164020e
SM
718static int64_t get_field_paths_lca_index(struct ctf_field_path *field_path1,
719 struct ctf_field_path *field_path2,
720 struct resolve_context *ctx)
44c440bc 721{
4164020e
SM
722 int64_t lca_index = 0;
723 uint64_t field_path1_len, field_path2_len;
724
0f5c5d5c
SM
725 BT_CPPLOGD_SPEC(ctx->logger,
726 "Finding lowest common ancestor (LCA) between two field paths: "
727 "field-path-1=\"{}\", field-path-2=\"{}\"",
728 *field_path1, *field_path2);
4164020e
SM
729
730 /*
731 * Start from both roots and find the first mismatch.
732 */
733 BT_ASSERT(field_path1->root == field_path2->root);
734 field_path1_len = field_path1->path->len;
735 field_path2_len = field_path2->path->len;
736
737 while (true) {
738 int64_t target_index, ctx_index;
739
740 if (lca_index == (int64_t) field_path2_len || lca_index == (int64_t) field_path1_len) {
741 /*
742 * This means that both field paths never split.
743 * This is invalid because the target cannot be
744 * an ancestor of the source.
745 */
0f5c5d5c
SM
746 BT_CPPLOGE_APPEND_CAUSE_SPEC(
747 ctx->logger,
4164020e 748 "Source field class is an ancestor of target field class or vice versa: "
0f5c5d5c
SM
749 "lca-index={}, "
750 "field-path-1-len={}, "
751 "field-path-2-len={}",
4164020e
SM
752 lca_index, field_path1_len, field_path2_len);
753 lca_index = -1;
754 break;
755 }
756
757 target_index = ctf_field_path_borrow_index_by_index(field_path1, lca_index);
758 ctx_index = ctf_field_path_borrow_index_by_index(field_path2, lca_index);
759
760 if (target_index != ctx_index) {
761 /* LCA index is the previous */
762 break;
763 }
764
765 lca_index++;
766 }
767
0f5c5d5c 768 BT_CPPLOGD_SPEC(ctx->logger, "Found LCA: lca-index={}", lca_index);
4164020e 769 return lca_index;
44c440bc
PP
770}
771
772/*
773 * Validates a target field path.
774 */
4164020e
SM
775static int validate_target_field_path(struct ctf_field_path *target_field_path,
776 struct ctf_field_class *target_fc,
777 struct resolve_context *ctx)
44c440bc 778{
4164020e
SM
779 int ret = 0;
780 struct ctf_field_path ctx_field_path;
781 uint64_t target_field_path_len = target_field_path->path->len;
782 int64_t lca_index;
783
784 /* Get context field path */
785 ctf_field_path_init(&ctx_field_path);
786 get_ctx_stack_field_path(ctx, &ctx_field_path);
787
788 /*
789 * Make sure the target is not a root.
790 */
791 if (target_field_path_len == 0) {
0f5c5d5c
SM
792 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
793 "Target field path's length is 0 (targeting the root).");
4164020e
SM
794 ret = -1;
795 goto end;
796 }
797
798 /*
799 * Make sure the root of the target field path is not located
800 * after the context field path's root.
801 */
802 if (target_field_path->root > ctx_field_path.root) {
0f5c5d5c
SM
803 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
804 "Target field class is located after source field class: "
805 "target-root={}, source-root={}",
806 target_field_path->root, ctx_field_path.root);
4164020e
SM
807 ret = -1;
808 goto end;
809 }
810
811 if (target_field_path->root == ctx_field_path.root) {
812 int64_t target_index, ctx_index;
813
814 /*
815 * Find the index of the lowest common ancestor of both field
816 * paths.
817 */
818 lca_index = get_field_paths_lca_index(target_field_path, &ctx_field_path, ctx);
819 if (lca_index < 0) {
0f5c5d5c 820 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger, "Cannot get least common ancestor.");
4164020e
SM
821 ret = -1;
822 goto end;
823 }
824
825 /*
826 * Make sure the target field path is located before the
827 * context field path.
828 */
829 target_index =
830 ctf_field_path_borrow_index_by_index(target_field_path, (uint64_t) lca_index);
831 ctx_index = ctf_field_path_borrow_index_by_index(&ctx_field_path, (uint64_t) lca_index);
832
833 if (target_index >= ctx_index) {
0f5c5d5c
SM
834 BT_CPPLOGE_APPEND_CAUSE_SPEC(
835 ctx->logger,
4164020e 836 "Target field class's index is greater than or equal to source field class's index in LCA: "
0f5c5d5c
SM
837 "lca-index={}, "
838 "target-index={}, "
839 "source-index={}",
4164020e
SM
840 lca_index, target_index, ctx_index);
841 ret = -1;
842 goto end;
843 }
844 }
845
846 /*
847 * Make sure the target class has the right class and properties.
848 */
849 switch (ctx->cur_fc->type) {
850 case CTF_FIELD_CLASS_TYPE_VARIANT:
851 if (target_fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
0f5c5d5c
SM
852 BT_CPPLOGE_APPEND_CAUSE_SPEC(
853 ctx->logger,
4164020e 854 "Variant field class's tag field class is not an enumeration field class: "
0f5c5d5c
SM
855 "tag-fc-addr={}, tag-fc-id={}",
856 fmt::ptr(target_fc), (int) target_fc->type);
4164020e
SM
857 ret = -1;
858 goto end;
859 }
860 break;
861 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
862 {
4164020e
SM
863 if (target_fc->type != CTF_FIELD_CLASS_TYPE_INT &&
864 target_fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
0f5c5d5c
SM
865 BT_CPPLOGE_APPEND_CAUSE_SPEC(
866 ctx->logger,
4164020e 867 "Sequence field class's length field class is not an unsigned integer field class: "
0f5c5d5c
SM
868 "length-fc-addr={}, length-fc-id={}",
869 fmt::ptr(target_fc), (int) target_fc->type);
4164020e
SM
870 ret = -1;
871 goto end;
872 }
873
ab003dc0
SM
874 ctf_field_class_int *int_fc = ctf_field_class_as_int(target_fc);
875
4164020e 876 if (int_fc->is_signed) {
0f5c5d5c
SM
877 BT_CPPLOGE_APPEND_CAUSE_SPEC(
878 ctx->logger,
4164020e 879 "Sequence field class's length field class is not an unsigned integer field class: "
0f5c5d5c
SM
880 "length-fc-addr={}, length-fc-id={}",
881 fmt::ptr(target_fc), (int) target_fc->type);
4164020e
SM
882 ret = -1;
883 goto end;
884 }
885 break;
886 }
887 default:
888 bt_common_abort();
889 }
44c440bc
PP
890
891end:
4164020e
SM
892 ctf_field_path_fini(&ctx_field_path);
893 return ret;
44c440bc
PP
894}
895
896/*
5cd6d0e5 897 * Resolves a variant or sequence field class `fc`.
44c440bc 898 */
4164020e
SM
899static int resolve_sequence_or_variant_field_class(struct ctf_field_class *fc,
900 struct resolve_context *ctx)
44c440bc 901{
4164020e
SM
902 int ret = 0;
903 const char *pathstr;
904 struct ctf_field_path target_field_path;
905 struct ctf_field_class *target_fc = NULL;
4164020e
SM
906
907 ctf_field_path_init(&target_field_path);
908
909 /* Get path string */
910 switch (fc->type) {
911 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
912 {
913 struct ctf_field_class_sequence *seq_fc = ctf_field_class_as_sequence(fc);
914 pathstr = seq_fc->length_ref->str;
915 break;
916 }
917 case CTF_FIELD_CLASS_TYPE_VARIANT:
918 {
919 struct ctf_field_class_variant *var_fc = ctf_field_class_as_variant(fc);
920 pathstr = var_fc->tag_ref->str;
921 break;
922 }
923 default:
924 bt_common_abort();
925 }
926
927 if (!pathstr) {
0f5c5d5c 928 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger, "Cannot get path string.");
4164020e
SM
929 ret = -1;
930 goto end;
931 }
932
933 /* Get target field path out of path string */
934 ret = pathstr_to_field_path(pathstr, &target_field_path, ctx);
935 if (ret) {
0f5c5d5c
SM
936 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
937 "Cannot get target field path for path string: "
938 "path=\"{}\"",
939 pathstr);
4164020e
SM
940 goto end;
941 }
942
4164020e
SM
943 /* Get target field class */
944 target_fc = field_path_to_field_class(&target_field_path, ctx);
945 if (!target_fc) {
0f5c5d5c
SM
946 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
947 "Cannot get target field class for path string: "
948 "path=\"{}\", target-field-path=\"{}\"",
949 pathstr, target_field_path);
4164020e
SM
950 ret = -1;
951 goto end;
952 }
953
954 ret = validate_target_field_path(&target_field_path, target_fc, ctx);
955 if (ret) {
0f5c5d5c
SM
956 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
957 "Invalid target field path for path string: "
958 "path=\"{}\", target-field-path=\"{}\"",
959 pathstr, target_field_path);
4164020e
SM
960 goto end;
961 }
962
963 /* Set target field path and target field class */
964 switch (fc->type) {
965 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
966 {
967 ctf_field_class_sequence *seq_fc = ctf_field_class_as_sequence(fc);
968
969 ctf_field_path_copy_content(&seq_fc->length_path, &target_field_path);
970 seq_fc->length_fc = ctf_field_class_as_int(target_fc);
971 break;
972 }
973 case CTF_FIELD_CLASS_TYPE_VARIANT:
974 {
975 ctf_field_class_variant *var_fc = ctf_field_class_as_variant(fc);
976
977 ctf_field_path_copy_content(&var_fc->tag_path, &target_field_path);
978 ctf_field_class_variant_set_tag_field_class(var_fc, ctf_field_class_as_enum(target_fc));
979 break;
980 }
981 default:
982 bt_common_abort();
983 }
44c440bc
PP
984
985end:
4164020e
SM
986 ctf_field_path_fini(&target_field_path);
987 return ret;
44c440bc
PP
988}
989
990/*
5cd6d0e5 991 * Resolves a field class `fc`.
44c440bc 992 */
4164020e 993static int resolve_field_class(struct ctf_field_class *fc, struct resolve_context *ctx)
44c440bc 994{
4164020e
SM
995 int ret = 0;
996
997 if (!fc) {
998 /* Field class is not available; still valid */
999 goto end;
1000 }
1001
1002 ctx->cur_fc = fc;
1003
1004 /* Resolve sequence/variant field class */
1005 switch (fc->type) {
1006 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
1007 case CTF_FIELD_CLASS_TYPE_VARIANT:
1008 ret = resolve_sequence_or_variant_field_class(fc, ctx);
1009 if (ret) {
0f5c5d5c
SM
1010 BT_CPPLOGE_APPEND_CAUSE_SPEC(
1011 ctx->logger,
4164020e 1012 "Cannot resolve sequence field class's length or variant field class's tag: "
0f5c5d5c
SM
1013 "ret={}, fc-addr={}",
1014 ret, fmt::ptr(fc));
4164020e
SM
1015 goto end;
1016 }
1017
1018 break;
1019 default:
1020 break;
1021 }
1022
1023 /* Recurse into compound classes */
1024 switch (fc->type) {
1025 case CTF_FIELD_CLASS_TYPE_STRUCT:
1026 case CTF_FIELD_CLASS_TYPE_VARIANT:
1027 case CTF_FIELD_CLASS_TYPE_SEQUENCE:
1028 case CTF_FIELD_CLASS_TYPE_ARRAY:
1029 {
1030 uint64_t i;
1031 uint64_t field_count = ctf_field_class_compound_get_field_class_count(fc);
1032
1033 ret = field_class_stack_push(ctx->field_class_stack, fc, ctx);
1034 if (ret) {
0f5c5d5c
SM
1035 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1036 "Cannot push field class on context's stack: "
1037 "fc-addr={}",
1038 fmt::ptr(fc));
4164020e
SM
1039 goto end;
1040 }
1041
1042 for (i = 0; i < field_count; i++) {
1043 struct ctf_field_class *child_fc =
1044 ctf_field_class_compound_borrow_field_class_by_index(fc, i);
1045
1046 BT_ASSERT(child_fc);
1047
1048 if (fc->type == CTF_FIELD_CLASS_TYPE_ARRAY ||
1049 fc->type == CTF_FIELD_CLASS_TYPE_SEQUENCE) {
1050 field_class_stack_peek(ctx->field_class_stack)->index = -1;
1051 } else {
1052 field_class_stack_peek(ctx->field_class_stack)->index = (int64_t) i;
1053 }
1054
0f5c5d5c
SM
1055 BT_CPPLOGD_SPEC(ctx->logger,
1056 "Resolving field class's child field class: "
1057 "parent-fc-addr={}, child-fc-addr={}, "
1058 "index={}, count={}",
1059 fmt::ptr(fc), fmt::ptr(child_fc), i, field_count);
4164020e
SM
1060 ret = resolve_field_class(child_fc, ctx);
1061 if (ret) {
1062 goto end;
1063 }
1064 }
1065
1066 field_class_stack_pop(ctx->field_class_stack, ctx);
1067 break;
1068 }
1069 default:
1070 break;
1071 }
44c440bc
PP
1072
1073end:
4164020e 1074 return ret;
44c440bc
PP
1075}
1076
1077/*
5cd6d0e5 1078 * Resolves the root field class corresponding to the scope `root_scope`.
44c440bc 1079 */
4164020e 1080static int resolve_root_class(enum ctf_scope root_scope, struct resolve_context *ctx)
44c440bc 1081{
4164020e 1082 int ret;
44c440bc 1083
4164020e
SM
1084 BT_ASSERT(field_class_stack_size(ctx->field_class_stack) == 0);
1085 ctx->root_scope = root_scope;
1086 ret = resolve_field_class(borrow_class_from_ctx(ctx, root_scope), ctx);
1087 ctx->root_scope = CTF_SCOPE_PACKET_UNKNOWN;
1088 return ret;
44c440bc
PP
1089}
1090
4164020e
SM
1091static int resolve_event_class_field_classes(struct resolve_context *ctx,
1092 struct ctf_event_class *ec)
44c440bc 1093{
4164020e
SM
1094 int ret = 0;
1095
1096 BT_ASSERT(!ctx->scopes.event_spec_context);
1097 BT_ASSERT(!ctx->scopes.event_payload);
1098
1099 if (ec->is_translated) {
1100 goto end;
1101 }
1102
1103 ctx->ec = ec;
1104 ctx->scopes.event_spec_context = ec->spec_context_fc;
1105 ret = resolve_root_class(CTF_SCOPE_EVENT_COMMON_CONTEXT, ctx);
1106 if (ret) {
0f5c5d5c
SM
1107 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1108 "Cannot resolve event specific context field class: "
1109 "ret={}",
1110 ret);
4164020e
SM
1111 goto end;
1112 }
1113
1114 ctx->scopes.event_payload = ec->payload_fc;
1115 ret = resolve_root_class(CTF_SCOPE_EVENT_PAYLOAD, ctx);
1116 if (ret) {
0f5c5d5c
SM
1117 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1118 "Cannot resolve event payload field class: "
1119 "ret={}",
1120 ret);
4164020e
SM
1121 goto end;
1122 }
44c440bc
PP
1123
1124end:
4164020e
SM
1125 ctx->scopes.event_spec_context = NULL;
1126 ctx->scopes.event_payload = NULL;
1127 ctx->ec = NULL;
1128 return ret;
44c440bc
PP
1129}
1130
4164020e
SM
1131static int resolve_stream_class_field_classes(struct resolve_context *ctx,
1132 struct ctf_stream_class *sc)
44c440bc 1133{
4164020e
SM
1134 int ret = 0;
1135 uint64_t i;
1136
1137 BT_ASSERT(!ctx->scopes.packet_context);
1138 BT_ASSERT(!ctx->scopes.event_header);
1139 BT_ASSERT(!ctx->scopes.event_common_context);
1140 ctx->sc = sc;
1141
1142 if (!sc->is_translated) {
1143 ctx->scopes.packet_context = sc->packet_context_fc;
1144 ret = resolve_root_class(CTF_SCOPE_PACKET_CONTEXT, ctx);
1145 if (ret) {
0f5c5d5c
SM
1146 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1147 "Cannot resolve packet context field class: "
1148 "ret={}",
1149 ret);
4164020e
SM
1150 goto end;
1151 }
1152
1153 ctx->scopes.event_header = sc->event_header_fc;
1154 ret = resolve_root_class(CTF_SCOPE_EVENT_HEADER, ctx);
1155 if (ret) {
0f5c5d5c
SM
1156 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1157 "Cannot resolve event header field class: "
1158 "ret={}",
1159 ret);
4164020e
SM
1160 goto end;
1161 }
1162
1163 ctx->scopes.event_common_context = sc->event_common_context_fc;
1164 ret = resolve_root_class(CTF_SCOPE_EVENT_COMMON_CONTEXT, ctx);
1165 if (ret) {
0f5c5d5c
SM
1166 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1167 "Cannot resolve event common context field class: "
1168 "ret={}",
1169 ret);
4164020e
SM
1170 goto end;
1171 }
1172 }
1173
1174 ctx->scopes.packet_context = sc->packet_context_fc;
1175 ctx->scopes.event_header = sc->event_header_fc;
1176 ctx->scopes.event_common_context = sc->event_common_context_fc;
1177
1178 for (i = 0; i < sc->event_classes->len; i++) {
1179 ctf_event_class *ec = (ctf_event_class *) sc->event_classes->pdata[i];
1180
1181 ret = resolve_event_class_field_classes(ctx, ec);
1182 if (ret) {
0f5c5d5c
SM
1183 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1184 "Cannot resolve event class's field classes: "
1185 "ec-id={}, ec-name=\"{}\"",
1186 ec->id, ec->name->str);
4164020e
SM
1187 goto end;
1188 }
1189 }
44c440bc
PP
1190
1191end:
4164020e
SM
1192 ctx->scopes.packet_context = NULL;
1193 ctx->scopes.event_header = NULL;
1194 ctx->scopes.event_common_context = NULL;
1195 ctx->sc = NULL;
1196 return ret;
44c440bc
PP
1197}
1198
0746848c 1199int ctf_trace_class_resolve_field_classes(struct ctf_trace_class *tc,
0f5c5d5c 1200 const bt2c::Logger& parentLogger)
44c440bc 1201{
4164020e
SM
1202 int ret = 0;
1203 uint64_t i;
1204
0f5c5d5c 1205 resolve_context local_ctx(parentLogger);
4164020e
SM
1206 local_ctx.tc = tc;
1207 local_ctx.scopes.packet_header = tc->packet_header_fc;
1208 local_ctx.root_scope = CTF_SCOPE_PACKET_HEADER;
1209
1210 struct resolve_context *ctx = &local_ctx;
1211
1212 /* Initialize class stack */
1213 ctx->field_class_stack = field_class_stack_create();
1214 if (!ctx->field_class_stack) {
0f5c5d5c 1215 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger, "Cannot create field class stack.");
4164020e
SM
1216 ret = -1;
1217 goto end;
1218 }
1219
1220 if (!tc->is_translated) {
1221 ctx->scopes.packet_header = tc->packet_header_fc;
1222 ret = resolve_root_class(CTF_SCOPE_PACKET_HEADER, ctx);
1223 if (ret) {
0f5c5d5c
SM
1224 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1225 "Cannot resolve packet header field class: "
1226 "ret={}",
1227 ret);
4164020e
SM
1228 goto end;
1229 }
1230 }
1231
1232 ctx->scopes.packet_header = tc->packet_header_fc;
1233
1234 for (i = 0; i < tc->stream_classes->len; i++) {
1235 ctf_stream_class *sc = (ctf_stream_class *) tc->stream_classes->pdata[i];
1236
1237 ret = resolve_stream_class_field_classes(ctx, sc);
1238 if (ret) {
0f5c5d5c
SM
1239 BT_CPPLOGE_APPEND_CAUSE_SPEC(ctx->logger,
1240 "Cannot resolve stream class's field classes: "
1241 "sc-id={}",
1242 sc->id);
4164020e
SM
1243 goto end;
1244 }
1245 }
44c440bc
PP
1246
1247end:
4164020e
SM
1248 field_class_stack_destroy(ctx->field_class_stack);
1249 return ret;
44c440bc 1250}
This page took 0.146514 seconds and 4 git commands to generate.