Fix: don't use BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_MEMORY_ERROR
[babeltrace.git] / src / plugins / common / param-validation / param-validation.c
1 /*
2 * Copyright 2019 EfficiOS Inc.
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 "param-validation.h"
24
25 #include <babeltrace2/babeltrace.h>
26 #include <glib.h>
27 #include <inttypes.h>
28
29 #include "common/common.h"
30
31 struct bt_param_validation_context {
32 gchar *error;
33 GArray *scope_stack;
34 };
35
36 struct validate_ctx_stack_element {
37 enum {
38 VALIDATE_CTX_STACK_ELEMENT_MAP,
39 VALIDATE_CTX_STACK_ELEMENT_ARRAY,
40 } type;
41
42 union {
43 const char *map_key_name;
44 uint64_t array_index;
45 };
46 };
47
48 static
49 void validate_ctx_push_map_scope(
50 struct bt_param_validation_context *ctx,
51 const char *key)
52 {
53 struct validate_ctx_stack_element stack_element = {
54 .type = VALIDATE_CTX_STACK_ELEMENT_MAP,
55 .map_key_name = key,
56 };
57
58 g_array_append_val(ctx->scope_stack, stack_element);
59 }
60
61 static
62 void validate_ctx_push_array_scope(
63 struct bt_param_validation_context *ctx, uint64_t index)
64 {
65 struct validate_ctx_stack_element stack_element = {
66 .type = VALIDATE_CTX_STACK_ELEMENT_ARRAY,
67 .array_index = index,
68 };
69
70 g_array_append_val(ctx->scope_stack, stack_element);
71 }
72
73 static
74 void validate_ctx_pop_scope(struct bt_param_validation_context *ctx)
75 {
76 BT_ASSERT(ctx->scope_stack->len > 0);
77
78 g_array_remove_index_fast(ctx->scope_stack, ctx->scope_stack->len - 1);
79 }
80
81 static
82 void append_scope_to_string(GString *str,
83 const struct validate_ctx_stack_element *elem,
84 bool first)
85 {
86 switch (elem->type) {
87 case VALIDATE_CTX_STACK_ELEMENT_MAP:
88 if (!first) {
89 g_string_append_c(str, '.');
90 }
91
92 g_string_append(str, elem->map_key_name);
93 break;
94 case VALIDATE_CTX_STACK_ELEMENT_ARRAY:
95 g_string_append_printf(str, "[%" PRIu64 "]", elem->array_index);
96 break;
97 default:
98 abort();
99 }
100 }
101
102 enum bt_param_validation_status bt_param_validation_error(
103 struct bt_param_validation_context *ctx,
104 const char *format, ...) {
105 va_list ap;
106 enum bt_param_validation_status status;
107
108 GString *str = g_string_new(NULL);
109 if (!str) {
110 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
111 goto end;
112 }
113
114 if (ctx->scope_stack->len > 0) {
115 guint i;
116
117 g_string_assign(str, "Error validating parameter `");
118
119 append_scope_to_string(str, &g_array_index(ctx->scope_stack,
120 struct validate_ctx_stack_element, 0), true);
121
122 for (i = 1; i < ctx->scope_stack->len; i++) {
123 append_scope_to_string(str,
124 &g_array_index(ctx->scope_stack,
125 struct validate_ctx_stack_element, i), false);
126 }
127
128 g_string_append(str, "`: ");
129 } else {
130 g_string_assign(str, "Error validating parameters: ");
131 }
132
133 va_start(ap, format);
134 g_string_append_vprintf(str, format, ap);
135 va_end(ap);
136
137 ctx->error = g_string_free(str, FALSE);
138 status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
139
140 end:
141 return status;
142 }
143
144 struct validate_map_value_data
145 {
146 GPtrArray *available_keys;
147 enum bt_param_validation_status status;
148 struct bt_param_validation_context *ctx;
149 };
150
151 static
152 enum bt_param_validation_status validate_value(
153 const bt_value *value,
154 const struct bt_param_validation_value_descr *descr,
155 struct bt_param_validation_context *ctx);
156
157 static
158 bt_bool validate_map_value_entry(const char *key,
159 const bt_value *value, void *v_data)
160 {
161 struct validate_map_value_data *data = v_data;
162 const struct bt_param_validation_map_value_entry_descr *candidate;
163 guint i;
164
165 /* Check if this key is in the available keys. */
166 for (i = 0; i < data->available_keys->len; i++) {
167 candidate = g_ptr_array_index(data->available_keys, i);
168
169 if (g_str_equal(key, candidate->key)) {
170 break;
171 }
172 }
173
174 if (i < data->available_keys->len) {
175 /* Key was found in available keys. */
176 g_ptr_array_remove_index_fast(data->available_keys, i);
177
178 /* Push key name as the scope. */
179 validate_ctx_push_map_scope(data->ctx, key);
180
181 /* Validate the value of the entry. */
182 data->status = validate_value(value, &candidate->value_descr,
183 data->ctx);
184
185 validate_ctx_pop_scope(data->ctx);
186 } else {
187 data->status = bt_param_validation_error(data->ctx,
188 "unexpected key `%s`.", key);
189 }
190
191 /* Continue iterating if everything is good so far. */
192 return data->status == BT_PARAM_VALIDATION_STATUS_OK;
193 }
194
195 static
196 enum bt_param_validation_status validate_map_value(
197 const struct bt_param_validation_map_value_descr *descr,
198 const bt_value *map,
199 struct bt_param_validation_context *ctx) {
200 enum bt_param_validation_status status;
201 struct validate_map_value_data data;
202 bt_value_map_foreach_entry_const_status foreach_entry_status;
203 GPtrArray *available_keys = NULL;
204 const struct bt_param_validation_map_value_entry_descr *descr_iter;
205 guint i;
206
207 BT_ASSERT(bt_value_get_type(map) == BT_VALUE_TYPE_MAP);
208
209 available_keys = g_ptr_array_new();
210 if (!available_keys) {
211 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
212 goto end;
213 }
214
215 for (descr_iter = descr->entries; descr_iter->key; descr_iter++) {
216 g_ptr_array_add(available_keys, (gpointer) descr_iter);
217 }
218
219 /* Initialize `status` to OK, in case the map is empty. */
220 data.status = BT_PARAM_VALIDATION_STATUS_OK;
221 data.available_keys = available_keys;
222 data.ctx = ctx;
223
224 foreach_entry_status = bt_value_map_foreach_entry_const(map,
225 validate_map_value_entry, &data);
226 if (foreach_entry_status == BT_VALUE_MAP_FOREACH_ENTRY_CONST_STATUS_INTERRUPTED) {
227 BT_ASSERT(data.status != BT_PARAM_VALIDATION_STATUS_OK);
228 status = data.status;
229 goto end;
230 }
231
232 BT_ASSERT(data.status == BT_PARAM_VALIDATION_STATUS_OK);
233
234 for (i = 0; i < data.available_keys->len; i++) {
235 const struct bt_param_validation_map_value_entry_descr *entry =
236 g_ptr_array_index(data.available_keys, i);
237
238 if (!entry->is_optional) {
239 status = bt_param_validation_error(ctx,
240 "missing mandatory entry `%s`",
241 entry->key);
242 goto end;
243 }
244 }
245
246 status = BT_PARAM_VALIDATION_STATUS_OK;
247
248 end:
249 g_ptr_array_free(available_keys, TRUE);
250 return status;
251 }
252
253 static
254 enum bt_param_validation_status validate_array_value(
255 const struct bt_param_validation_array_value_descr *descr,
256 const bt_value *array,
257 struct bt_param_validation_context *ctx) {
258 enum bt_param_validation_status status;
259 uint64_t i;
260
261 BT_ASSERT(bt_value_get_type(array) == BT_VALUE_TYPE_ARRAY);
262
263 if (bt_value_array_get_length(array) < descr->min_length) {
264 status = bt_param_validation_error(ctx,
265 "array is smaller than the minimum length: "
266 "array-length=%" PRIu64 ", min-length=%" PRIu64,
267 bt_value_array_get_length(array),
268 descr->min_length);
269 goto end;
270 }
271
272 if (bt_value_array_get_length(array) > descr->max_length) {
273 status = bt_param_validation_error(ctx,
274 "array is larger than the maximum length: "
275 "array-length=%" PRIu64 ", max-length=%" PRIu64,
276 bt_value_array_get_length(array),
277 descr->max_length);
278 goto end;
279 }
280
281 for (i = 0; i < bt_value_array_get_length(array); i++) {
282 const bt_value *element =
283 bt_value_array_borrow_element_by_index_const(array, i);
284
285 validate_ctx_push_array_scope(ctx, i);
286
287 status = validate_value(element, descr->element_type, ctx);
288
289 validate_ctx_pop_scope(ctx);
290
291 if (status != BT_PARAM_VALIDATION_STATUS_OK) {
292 goto end;
293 }
294 }
295
296 status = BT_PARAM_VALIDATION_STATUS_OK;
297
298 end:
299 return status;
300 }
301
302 static
303 enum bt_param_validation_status validate_string_value(
304 const struct bt_param_validation_string_value_descr *descr,
305 const bt_value *string,
306 struct bt_param_validation_context *ctx) {
307 enum bt_param_validation_status status;
308 const char *s = bt_value_string_get(string);
309 gchar *joined_choices = NULL;
310
311 BT_ASSERT(bt_value_get_type(string) == BT_VALUE_TYPE_STRING);
312
313 if (descr->choices) {
314 const char **choice;
315
316 for (choice = descr->choices; *choice; choice++) {
317 if (strcmp(s, *choice) == 0) {
318 break;
319 }
320 }
321
322 if (!*choice) {
323 /*
324 * g_strjoinv takes a gchar **, but it doesn't modify
325 * the array of the strings (yet).
326 */
327 joined_choices = g_strjoinv(", ", (gchar **) descr->choices);
328 if (!joined_choices) {
329 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
330 goto end;
331 }
332
333 status = bt_param_validation_error(ctx,
334 "string is not amongst the available choices: "
335 "string=%s, choices=[%s]", s, joined_choices);
336 goto end;
337 }
338 }
339
340 status = BT_PARAM_VALIDATION_STATUS_OK;
341 end:
342 g_free(joined_choices);
343
344 return status;
345 }
346
347 static
348 enum bt_param_validation_status validate_value(
349 const bt_value *value,
350 const struct bt_param_validation_value_descr *descr,
351 struct bt_param_validation_context *ctx) {
352 enum bt_param_validation_status status;
353
354 /* If there is a custom validation func, we call it and ignore the rest. */
355 if (descr->validation_func) {
356 status = descr->validation_func(value, ctx);
357
358 if (status == BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR) {
359 BT_ASSERT(ctx->error);
360 }
361
362 goto end;
363 }
364
365 if (bt_value_get_type(value) != descr->type) {
366 bt_param_validation_error(ctx,
367 "unexpected type: expected-type=%s, actual-type=%s",
368 bt_common_value_type_string(descr->type),
369 bt_common_value_type_string(bt_value_get_type(value)));
370 status = BT_PARAM_VALIDATION_STATUS_VALIDATION_ERROR;
371 goto end;
372 }
373
374 switch (bt_value_get_type(value)) {
375 case BT_VALUE_TYPE_MAP:
376 status = validate_map_value(&descr->map, value, ctx);
377 break;
378 case BT_VALUE_TYPE_ARRAY:
379 status = validate_array_value(&descr->array, value, ctx);
380 break;
381 case BT_VALUE_TYPE_STRING:
382 status = validate_string_value(&descr->string, value, ctx);
383 break;
384 default:
385 status = BT_PARAM_VALIDATION_STATUS_OK;
386 break;
387 }
388
389 end:
390 return status;
391 }
392
393 enum bt_param_validation_status bt_param_validation_validate(
394 const bt_value *params,
395 const struct bt_param_validation_map_value_entry_descr *entries,
396 gchar **error) {
397 struct bt_param_validation_context ctx;
398 struct bt_param_validation_map_value_descr map_value_descr;
399 enum bt_param_validation_status status;
400
401 ctx.error = NULL;
402 ctx.scope_stack = g_array_new(FALSE, FALSE,
403 sizeof(struct validate_ctx_stack_element));
404 g_ptr_array_new_with_free_func(g_free);
405 if (!ctx.scope_stack) {
406 status = BT_PARAM_VALIDATION_STATUS_MEMORY_ERROR;
407 goto end;
408 }
409
410 map_value_descr.entries = entries;
411
412 status = validate_map_value(&map_value_descr, params, &ctx);
413
414 end:
415 *error = ctx.error;
416 ctx.error = NULL;
417
418 return status;
419 }
This page took 0.03741 seconds and 4 git commands to generate.