Fix: field-class.c: dereference before `NULL` check
[babeltrace.git] / src / cli / babeltrace2-cfg-cli-params-arg.c
CommitLineData
8bd04432
PP
1/*
2 * Copyright 2016-2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23#include <errno.h>
24#include <stdlib.h>
25#include <string.h>
26#include "common/assert.h"
27#include <stdio.h>
28#include <stdbool.h>
29#include <inttypes.h>
30#include <babeltrace2/babeltrace.h>
31#include "common/common.h"
8bd04432
PP
32#include <glib.h>
33#include <sys/types.h>
34#include "babeltrace2-cfg.h"
35#include "babeltrace2-cfg-cli-args.h"
36#include "babeltrace2-cfg-cli-args-connect.h"
37#include "common/version.h"
38
39/* INI-style parsing FSM states */
40enum ini_parsing_fsm_state {
41 /* Expect a map key (identifier) */
42 INI_EXPECT_MAP_KEY,
43
44 /* Expect an equal character (`=`) */
45 INI_EXPECT_EQUAL,
46
47 /* Expect a value */
48 INI_EXPECT_VALUE,
49
50 /* Expect a comma character (`,`) */
51 INI_EXPECT_COMMA,
52};
53
54/* INI-style parsing state variables */
55struct ini_parsing_state {
56 /* Lexical scanner (owned by this) */
57 GScanner *scanner;
58
59 /* Output map value object being filled (owned by this) */
60 bt_value *params;
61
62 /* Next expected FSM state */
63 enum ini_parsing_fsm_state expecting;
64
65 /* Last decoded map key (owned by this) */
66 GString *last_map_key;
67
68 /* Complete INI-style string to parse */
69 const char *arg;
70
71 /* Error buffer (weak) */
72 GString *ini_error;
73};
74
75/*
76 * Appends an "expecting token" error to the INI-style parsing state's
77 * error buffer.
78 */
79static
80void ini_append_error_expecting(struct ini_parsing_state *state,
81 GScanner *scanner, const char *expecting)
82{
83 size_t i;
84 size_t pos;
85
86 g_string_append_printf(state->ini_error, "Expecting %s:\n", expecting);
87
88 /* Only append error if there's one line */
5084732e 89 if (strchr(state->arg, '\n') || strlen(state->arg) == 0) {
8bd04432
PP
90 return;
91 }
92
93 g_string_append_printf(state->ini_error, "\n %s\n", state->arg);
94 pos = g_scanner_cur_position(scanner) + 4;
95
96 if (!g_scanner_eof(scanner)) {
97 pos--;
98 }
99
100 for (i = 0; i < pos; ++i) {
101 g_string_append_printf(state->ini_error, " ");
102 }
103
104 g_string_append_printf(state->ini_error, "^\n\n");
105}
106
107static
108void ini_append_oom_error(GString *error)
109{
110 BT_ASSERT(error);
111 g_string_append(error, "Out of memory\n");
112}
113
114/*
115 * Parses the next token as an unsigned integer.
116 */
117static
118bt_value *ini_parse_uint(struct ini_parsing_state *state)
119{
120 bt_value *value = NULL;
121 GTokenType token_type = g_scanner_get_next_token(state->scanner);
122
123 if (token_type != G_TOKEN_INT) {
124 ini_append_error_expecting(state, state->scanner,
125 "integer value");
126 goto end;
127 }
128
9c08c816 129 value = bt_value_integer_unsigned_create_init(
8bd04432
PP
130 state->scanner->value.v_int64);
131
132end:
133 return value;
134}
135
136/*
137 * Parses the next token as a number and returns its negation.
138 */
139static
140bt_value *ini_parse_neg_number(struct ini_parsing_state *state)
141{
142 bt_value *value = NULL;
143 GTokenType token_type = g_scanner_get_next_token(state->scanner);
144
145 switch (token_type) {
146 case G_TOKEN_INT:
147 {
148 /* Negative integer */
149 uint64_t int_val = state->scanner->value.v_int64;
150
151 if (int_val > (((uint64_t) INT64_MAX) + 1)) {
152 g_string_append_printf(state->ini_error,
153 "Integer value -%" PRIu64 " is outside the range of a 64-bit signed integer\n",
154 int_val);
155 } else {
9c08c816 156 value = bt_value_integer_signed_create_init(
8bd04432
PP
157 -((int64_t) int_val));
158 }
159
160 break;
161 }
162 case G_TOKEN_FLOAT:
163 /* Negative floating point number */
164 value = bt_value_real_create_init(
165 -state->scanner->value.v_float);
166 break;
167 default:
168 ini_append_error_expecting(state, state->scanner, "value");
169 break;
170 }
171
172 return value;
173}
174
175static bt_value *ini_parse_value(struct ini_parsing_state *state);
176
177/*
178 * Parses the current and following tokens as an array. Arrays are
179 * formatted as an opening `[`, a list of comma-separated values, and a
180 * closing `]`. For convenience, this function supports an optional
181 * trailing comma after the last value.
182 *
183 * The current token of the parser must be the opening square bracket
184 * (`[`) of the array.
185 */
186static
187bt_value *ini_parse_array(struct ini_parsing_state *state)
188{
189 bt_value *array_value;
190 GTokenType token_type;
191
192 /* The `[` character must have already been ingested */
193 BT_ASSERT(g_scanner_cur_token(state->scanner) == G_TOKEN_CHAR);
194 BT_ASSERT(g_scanner_cur_value(state->scanner).v_char == '[');
195
196 array_value = bt_value_array_create ();
197 if (!array_value) {
198 ini_append_oom_error(state->ini_error);
199 goto error;
200 }
201
202 token_type = g_scanner_get_next_token(state->scanner);
203
204 /* While the current token is not a `]` */
205 while (!(token_type == G_TOKEN_CHAR &&
206 g_scanner_cur_value(state->scanner).v_char == ']')) {
207 bt_value *item_value;
208 bt_value_array_append_element_status append_status;
209
210 /* Parse the item... */
211 item_value = ini_parse_value(state);
212 if (!item_value) {
213 goto error;
214 }
215
216 /* ... and add it to the result array */
217 append_status = bt_value_array_append_element(array_value,
218 item_value);
219 BT_VALUE_PUT_REF_AND_RESET(item_value);
220 if (append_status < 0) {
221 goto error;
222 }
223
224 /*
225 * Ingest the token following the value. It should be
226 * either a comma or closing square bracket.
227 */
228 token_type = g_scanner_get_next_token(state->scanner);
229 if (token_type == G_TOKEN_CHAR &&
230 g_scanner_cur_value(state->scanner).v_char == ',') {
231 /*
232 * Ingest the token following the comma. If it
233 * happens to be a closing square bracket, exit
234 * the loop and we are done (we allow trailing
235 * commas). Otherwise, we are ready for the next
236 * ini_parse_value() call.
237 */
238 token_type = g_scanner_get_next_token(state->scanner);
239 } else if (token_type != G_TOKEN_CHAR ||
240 g_scanner_cur_value(state->scanner).v_char != ']') {
241 ini_append_error_expecting(state, state->scanner,
242 "`,` or `]`");
243 goto error;
244 }
245 }
246
247 goto end;
248
249error:
250 BT_VALUE_PUT_REF_AND_RESET(array_value);
251
252end:
253 return array_value;
254}
255
256/*
257 * Parses the current token (and the following ones if needed) as a
258 * value, returning it as a `bt_value *`.
259 */
260static
261bt_value *ini_parse_value(struct ini_parsing_state *state)
262{
263 bt_value *value = NULL;
264 GTokenType token_type = state->scanner->token;
265
266 switch (token_type) {
267 case G_TOKEN_CHAR:
268 if (state->scanner->value.v_char == '-') {
269 /* Negative number */
270 value = ini_parse_neg_number(state);
271 } else if (state->scanner->value.v_char == '+') {
272 /* Unsigned integer */
273 value = ini_parse_uint(state);
274 } else if (state->scanner->value.v_char == '[') {
275 /* Array */
276 value = ini_parse_array(state);
277 } else {
278 ini_append_error_expecting(state, state->scanner, "value");
279 goto end;
280 }
281
282 break;
283 case G_TOKEN_INT:
284 {
285 /* Positive, signed integer */
286 uint64_t int_val = state->scanner->value.v_int64;
287
288 if (int_val > INT64_MAX) {
289 g_string_append_printf(state->ini_error,
290 "Integer value %" PRIu64 " is outside the range of a 64-bit signed integer\n",
291 int_val);
292 goto end;
293 } else {
9c08c816 294 value = bt_value_integer_signed_create_init(
8bd04432
PP
295 (int64_t) int_val);
296 }
297
298 break;
299 }
300 case G_TOKEN_FLOAT:
301 /* Positive floating point number */
302 value = bt_value_real_create_init(state->scanner->value.v_float);
303 break;
304 case G_TOKEN_STRING:
305 /* Quoted string */
306 value = bt_value_string_create_init(state->scanner->value.v_string);
307 break;
308 case G_TOKEN_IDENTIFIER:
309 {
310 /*
311 * Using symbols would be appropriate here, but said
312 * symbols are allowed as map key, so it's easier to
313 * consider everything an identifier.
314 *
315 * If one of the known symbols is not recognized here,
316 * then fall back to creating a string value.
317 */
318 const char *id = state->scanner->value.v_identifier;
319
2242b43d
PP
320 if (strcmp(id, "null") == 0 || strcmp(id, "NULL") == 0 ||
321 strcmp(id, "nul") == 0) {
8bd04432 322 value = bt_value_null;
2242b43d
PP
323 } else if (strcmp(id, "true") == 0 || strcmp(id, "TRUE") == 0 ||
324 strcmp(id, "yes") == 0 ||
325 strcmp(id, "YES") == 0) {
8bd04432 326 value = bt_value_bool_create_init(true);
2242b43d
PP
327 } else if (strcmp(id, "false") == 0 ||
328 strcmp(id, "FALSE") == 0 ||
329 strcmp(id, "no") == 0 ||
330 strcmp(id, "NO") == 0) {
8bd04432
PP
331 value = bt_value_bool_create_init(false);
332 } else {
333 value = bt_value_string_create_init(id);
334 }
335 break;
336 }
337 default:
338 /* Unset return value variable will trigger the error */
339 ini_append_error_expecting(state, state->scanner, "value");
340 break;
341 }
342
343end:
344 return value;
345}
346
347/*
348 * Handles the current state of the INI parser.
349 *
350 * Returns 0 to continue, 1 to end, or a negative value on error.
351 */
352static
353int ini_handle_state(struct ini_parsing_state *state)
354{
355 int ret = 0;
356 GTokenType token_type;
357 bt_value *value = NULL;
358
359 token_type = g_scanner_get_next_token(state->scanner);
360 if (token_type == G_TOKEN_EOF) {
361 if (state->expecting != INI_EXPECT_COMMA) {
362 switch (state->expecting) {
363 case INI_EXPECT_EQUAL:
364 ini_append_error_expecting(state,
365 state->scanner, "`=`");
366 break;
367 case INI_EXPECT_VALUE:
368 ini_append_error_expecting(state,
369 state->scanner, "value");
370 break;
371 case INI_EXPECT_MAP_KEY:
372 ini_append_error_expecting(state,
373 state->scanner, "unquoted map key");
374 break;
375 default:
376 break;
377 }
378 goto error;
379 }
380
381 /* We're done! */
382 ret = 1;
383 goto success;
384 }
385
386 switch (state->expecting) {
387 case INI_EXPECT_MAP_KEY:
388 if (token_type != G_TOKEN_IDENTIFIER) {
389 ini_append_error_expecting(state, state->scanner,
390 "unquoted map key");
391 goto error;
392 }
393
394 g_string_assign(state->last_map_key,
395 state->scanner->value.v_identifier);
396
397 if (bt_value_map_has_entry(state->params,
398 state->last_map_key->str)) {
399 g_string_append_printf(state->ini_error,
400 "Duplicate parameter key: `%s`\n",
401 state->last_map_key->str);
402 goto error;
403 }
404
405 state->expecting = INI_EXPECT_EQUAL;
406 goto success;
407 case INI_EXPECT_EQUAL:
408 if (token_type != G_TOKEN_CHAR) {
409 ini_append_error_expecting(state,
410 state->scanner, "'='");
411 goto error;
412 }
413
414 if (state->scanner->value.v_char != '=') {
415 ini_append_error_expecting(state,
416 state->scanner, "'='");
417 goto error;
418 }
419
420 state->expecting = INI_EXPECT_VALUE;
421 goto success;
422 case INI_EXPECT_VALUE:
423 {
424 value = ini_parse_value(state);
425 if (!value) {
426 goto error;
427 }
428
429 state->expecting = INI_EXPECT_COMMA;
430 goto success;
431 }
432 case INI_EXPECT_COMMA:
433 if (token_type != G_TOKEN_CHAR) {
434 ini_append_error_expecting(state,
435 state->scanner, "','");
436 goto error;
437 }
438
439 if (state->scanner->value.v_char != ',') {
440 ini_append_error_expecting(state,
441 state->scanner, "','");
442 goto error;
443 }
444
445 state->expecting = INI_EXPECT_MAP_KEY;
446 goto success;
447 default:
448 abort();
449 }
450
451error:
452 ret = -1;
453 goto end;
454
455success:
456 if (value) {
457 if (bt_value_map_insert_entry(state->params,
458 state->last_map_key->str, value)) {
459 /* Only override return value on error */
460 ret = -1;
461 }
462 }
463
464end:
465 BT_VALUE_PUT_REF_AND_RESET(value);
466 return ret;
467}
468
469/*
470 * Converts an INI-style argument to an equivalent map value object.
471 *
472 * Return value is owned by the caller.
473 */
474BT_HIDDEN
475bt_value *cli_value_from_arg(const char *arg, GString *ini_error)
476{
477 /* Lexical scanner configuration */
478 GScannerConfig scanner_config = {
479 /* Skip whitespaces */
480 .cset_skip_characters = " \t\n",
481
482 /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
483 .cset_identifier_first =
484 G_CSET_a_2_z
485 "_"
486 G_CSET_A_2_Z,
487 .cset_identifier_nth =
488 G_CSET_a_2_z
489 "_0123456789-.:"
490 G_CSET_A_2_Z,
491
492 /* "hello" and "Hello" two different keys */
493 .case_sensitive = TRUE,
494
495 /* No comments */
496 .cpair_comment_single = NULL,
497 .skip_comment_multi = TRUE,
498 .skip_comment_single = TRUE,
499 .scan_comment_multi = FALSE,
500
501 /*
502 * Do scan identifiers, including 1-char identifiers,
503 * but NULL is a normal identifier.
504 */
505 .scan_identifier = TRUE,
506 .scan_identifier_1char = TRUE,
507 .scan_identifier_NULL = FALSE,
508
509 /*
510 * No specific symbols: null and boolean "symbols" are
511 * scanned as plain identifiers.
512 */
513 .scan_symbols = FALSE,
514 .symbol_2_token = FALSE,
515 .scope_0_fallback = FALSE,
516
517 /*
518 * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
519 * integers prefixed with "$".
520 */
521 .scan_binary = TRUE,
522 .scan_octal = TRUE,
523 .scan_float = TRUE,
524 .scan_hex = TRUE,
525 .scan_hex_dollar = FALSE,
526
527 /* Convert scanned numbers to integer tokens */
528 .numbers_2_int = TRUE,
529
530 /* Support both integers and floating point numbers */
531 .int_2_float = FALSE,
532
533 /* Scan integers as 64-bit signed integers */
534 .store_int64 = TRUE,
535
536 /* Only scan double-quoted strings */
537 .scan_string_sq = FALSE,
538 .scan_string_dq = TRUE,
539
540 /* Do not converter identifiers to string tokens */
541 .identifier_2_string = FALSE,
542
543 /* Scan characters as `G_TOKEN_CHAR` token */
544 .char_2_token = FALSE,
545 };
546 struct ini_parsing_state state = {
547 .scanner = NULL,
548 .params = NULL,
549 .expecting = INI_EXPECT_MAP_KEY,
550 .arg = arg,
551 .ini_error = ini_error,
552 };
553
554 BT_ASSERT(ini_error);
555 g_string_assign(ini_error, "");
556 state.params = bt_value_map_create();
557 if (!state.params) {
558 ini_append_oom_error(ini_error);
559 goto error;
560 }
561
562 state.scanner = g_scanner_new(&scanner_config);
563 if (!state.scanner) {
564 ini_append_oom_error(ini_error);
565 goto error;
566 }
567
568 state.last_map_key = g_string_new(NULL);
569 if (!state.last_map_key) {
570 ini_append_oom_error(ini_error);
571 goto error;
572 }
573
574 /* Let the scan begin */
575 g_scanner_input_text(state.scanner, arg, strlen(arg));
576
577 while (true) {
578 int ret = ini_handle_state(&state);
579
580 if (ret < 0) {
581 /* Error */
582 goto error;
583 } else if (ret > 0) {
584 /* Done */
585 break;
586 }
587 }
588
589 goto end;
590
591error:
592 BT_VALUE_PUT_REF_AND_RESET(state.params);
593
594end:
595 if (state.scanner) {
596 g_scanner_destroy(state.scanner);
597 }
598
599 if (state.last_map_key) {
600 g_string_free(state.last_map_key, TRUE);
601 }
602
603 return state.params;
604}
This page took 0.048156 seconds and 4 git commands to generate.