From 364d7d06c087e91d84a4b18dc7f392317280eddf Mon Sep 17 00:00:00 2001 From: Wenduo Wang Date: Fri, 10 May 2024 16:35:12 +0000 Subject: [PATCH] Introduce new JSON parser utility This patch introduces a utility in OPAL based on the 3rd-party project https://github.com/json-parser/json-parser.git The utility provides APIs to read JSON into memory along with getters to retrieve C values. Signed-off-by: Wenduo Wang --- config/opal_config_files.m4 | 4 +- opal/util/Makefile.am | 12 +- opal/util/json/3rd-party/json.c | 1062 +++++++++++++++++++++++++++++++ opal/util/json/3rd-party/json.h | 288 +++++++++ opal/util/json/Makefile.am | 17 + opal/util/json/help-json.txt | 28 + opal/util/json/opal_json.c | 263 ++++++++ opal/util/json/opal_json.h | 154 +++++ test/support/support.c | 42 +- test/support/support.h | 5 + test/util/Makefile.am | 11 +- test/util/opal_json.c | 255 ++++++++ 12 files changed, 2118 insertions(+), 23 deletions(-) create mode 100644 opal/util/json/3rd-party/json.c create mode 100644 opal/util/json/3rd-party/json.h create mode 100644 opal/util/json/Makefile.am create mode 100644 opal/util/json/help-json.txt create mode 100644 opal/util/json/opal_json.c create mode 100644 opal/util/json/opal_json.h create mode 100644 test/util/opal_json.c diff --git a/config/opal_config_files.m4 b/config/opal_config_files.m4 index 78358d998c1..cf4e658ef9f 100644 --- a/config/opal_config_files.m4 +++ b/config/opal_config_files.m4 @@ -4,7 +4,8 @@ # Copyright (c) 2020 The University of Tennessee and The University # of Tennessee Research Foundation. All rights # reserved. -# Copyright (c) 2022 Amazon.com, Inc. or its affiliates. All Rights reserved. +# Copyright (c) 2022-2024 Amazon.com, Inc. or its affiliates. +# All Rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -21,6 +22,7 @@ AC_DEFUN([OPAL_CONFIG_FILES],[ opal/include/Makefile opal/datatype/Makefile opal/util/Makefile + opal/util/json/Makefile opal/util/keyval/Makefile opal/mca/base/Makefile opal/tools/wrappers/Makefile diff --git a/opal/util/Makefile.am b/opal/util/Makefile.am index 23f6b0ccd67..2c62bb50351 100644 --- a/opal/util/Makefile.am +++ b/opal/util/Makefile.am @@ -17,7 +17,7 @@ # Copyright (c) 2016 Research Organization for Information Science # and Technology (RIST). All rights reserved. # Copyright (c) 2016-2017 IBM Corporation. All rights reserved. -# Copyright (c) 2020 Amazon.com, Inc. or its affiliates. +# Copyright (c) 2020-2024 Amazon.com, Inc. or its affiliates. # All Rights reserved. # Copyright (c) 2021 Google, LLC. All rights reserved. # $COPYRIGHT$ @@ -27,9 +27,13 @@ # $HEADER$ # -SUBDIRS = keyval +SUBDIRS = \ + json \ + keyval -dist_opaldata_DATA = help-opal-util.txt +dist_opaldata_DATA = \ + help-opal-util.txt \ + json/help-json.txt AM_LFLAGS = -Popal_show_help_yy LEX_OUTPUT_ROOT = lex.opal_show_help_yy @@ -127,8 +131,10 @@ libopalutil_core_la_SOURCES += timings.c endif libopalutil_core_la_LIBADD = \ + json/libopalutil_json.la \ keyval/libopalutilkeyval.la libopalutil_core_la_DEPENDENCIES = \ + json/libopalutil_json.la \ keyval/libopalutilkeyval.la # Conditionally install the header files diff --git a/opal/util/json/3rd-party/json.c b/opal/util/json/3rd-party/json.c new file mode 100644 index 00000000000..83247de7fd8 --- /dev/null +++ b/opal/util/json/3rd-party/json.c @@ -0,0 +1,1062 @@ +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012-2021 the json-parser authors All rights reserved. + * https://github.com/json-parser/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "json.h" + +#ifdef _MSC_VER + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + /* C99 might give us uintptr_t and UINTPTR_MAX but they also might not be provided */ + #include +#endif + +#ifndef JSON_INT_T_OVERRIDDEN + #if defined(_MSC_VER) + /* https://docs.microsoft.com/en-us/cpp/cpp/data-type-ranges */ + #define JSON_INT_MAX 9223372036854775807LL + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + /* C99 */ + #define JSON_INT_MAX INT_FAST64_MAX + #else + /* C89 */ + #include + #define JSON_INT_MAX LONG_MAX + #endif +#endif + +#ifndef JSON_INT_MAX +#define JSON_INT_MAX (json_int_t)(((unsigned json_int_t)(-1)) / (unsigned json_int_t)2); +#endif + +typedef unsigned int json_uchar; + +__attribute__((visibility ("hidden"))) +const struct _json_value json_value_none; + +static unsigned char hex_value (json_char c) +{ + if (isdigit((unsigned char)c)) + return c - '0'; + + switch (c) { + case 'a': case 'A': return 0x0A; + case 'b': case 'B': return 0x0B; + case 'c': case 'C': return 0x0C; + case 'd': case 'D': return 0x0D; + case 'e': case 'E': return 0x0E; + case 'f': case 'F': return 0x0F; + default: return 0xFF; + } +} + +static int would_overflow (json_int_t value, json_char b) +{ + return ((JSON_INT_MAX - (b - '0')) / 10 ) < value; +} + +typedef struct +{ + size_t used_memory; + + json_settings settings; + int first_pass; + + const json_char * ptr; + unsigned int cur_line, cur_col; + +} json_state; + +static void * default_alloc (size_t size, int zero, void * user_data) +{ + (void)user_data; /* ignore unused-parameter warn */ + return zero ? calloc (1, size) : malloc (size); +} + +static void default_free (void * ptr, void * user_data) +{ + (void)user_data; /* ignore unused-parameter warn */ + free (ptr); +} + +static void * json_alloc (json_state * state, size_t size, int zero) +{ + if ((ULONG_MAX - 8 - state->used_memory) < size) + return 0; + + if (state->settings.max_memory + && (state->used_memory += size) > state->settings.max_memory) + { + return 0; + } + + return state->settings.mem_alloc (size, zero, state->settings.user_data); +} + +static int new_value (json_state * state, + json_value ** top, json_value ** root, json_value ** alloc, + json_type type) +{ + json_value * value; + size_t values_size; + + if (!state->first_pass) + { + value = *top = *alloc; + *alloc = (*alloc)->_reserved.next_alloc; + + if (!*root) + *root = value; + + switch (value->type) + { + case json_array: + + if (value->u.array.length == 0) + break; + + if (! (value->u.array.values = (json_value **) json_alloc + (state, value->u.array.length * sizeof (json_value *), 0)) ) + { + return 0; + } + + value->u.array.length = 0; + break; + + case json_object: + + if (value->u.object.length == 0) + break; + + values_size = sizeof (*value->u.object.values) * value->u.object.length; + + if (! (value->u.object.values = (json_object_entry *) json_alloc + #ifdef UINTPTR_MAX + (state, values_size + ((uintptr_t) value->u.object.values), 0)) ) + #else + (state, values_size + ((size_t) value->u.object.values), 0)) ) + #endif + { + return 0; + } + + value->_reserved.object_mem = (void *) (((char *) value->u.object.values) + values_size); + + value->u.object.length = 0; + break; + + case json_string: + + if (! (value->u.string.ptr = (json_char *) json_alloc + (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) + { + return 0; + } + + value->u.string.length = 0; + break; + + default: + break; + }; + + return 1; + } + + if (! (value = (json_value *) json_alloc + (state, sizeof (json_value) + state->settings.value_extra, 1))) + { + return 0; + } + + if (!*root) + *root = value; + + value->type = type; + value->parent = *top; + + #ifdef JSON_TRACK_SOURCE + value->line = state->cur_line; + value->col = state->cur_col; + #endif + + if (*alloc) + (*alloc)->_reserved.next_alloc = value; + + *alloc = *top = value; + + return 1; +} + +#define whitespace \ + case '\n': ++ state.cur_line; state.cur_col = 0; /* FALLTHRU */ \ + case ' ': /* FALLTHRU */ case '\t': /* FALLTHRU */ case '\r' + +#define string_add(b) \ + do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); + +#define line_and_col \ + state.cur_line, state.cur_col + +static const long + flag_next = 1 << 0, + flag_reproc = 1 << 1, + flag_need_comma = 1 << 2, + flag_seek_value = 1 << 3, + flag_escaped = 1 << 4, + flag_string = 1 << 5, + flag_need_colon = 1 << 6, + flag_done = 1 << 7, + flag_num_negative = 1 << 8, + flag_num_zero = 1 << 9, + flag_num_e = 1 << 10, + flag_num_e_got_sign = 1 << 11, + flag_num_e_negative = 1 << 12, + flag_line_comment = 1 << 13, + flag_block_comment = 1 << 14, + flag_num_got_decimal = 1 << 15; + +__attribute__((visibility ("hidden"))) +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error_buf) +{ + char error [json_error_max]; + const json_char * end; + json_value * top, * root, * alloc = 0; + json_state state = { 0 }; + long flags = 0; + int num_digits = 0; + double num_e = 0, num_fraction = 0; + + /* Skip UTF-8 BOM + */ + if (length >= 3 && ((unsigned char) json [0]) == 0xEF + && ((unsigned char) json [1]) == 0xBB + && ((unsigned char) json [2]) == 0xBF) + { + json += 3; + length -= 3; + } + + error[0] = '\0'; + end = (json + length); + + memcpy (&state.settings, settings, sizeof (json_settings)); + + if (!state.settings.mem_alloc) + state.settings.mem_alloc = default_alloc; + + if (!state.settings.mem_free) + state.settings.mem_free = default_free; + + for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) + { + json_uchar uchar; + unsigned char uc_b1, uc_b2, uc_b3, uc_b4; + json_char * string = 0; + unsigned int string_length = 0; + + top = root = 0; + flags = flag_seek_value; + + state.cur_line = 1; + + for (state.ptr = json ;; ++ state.ptr) + { + json_char b = (state.ptr == end ? 0 : *state.ptr); + + if (flags & flag_string) + { + if (!b) + { sprintf (error, "%u:%u: Unexpected EOF in string", line_and_col); + goto e_failed; + } + + if (string_length > UINT_MAX - 8) + goto e_overflow; + + if (flags & flag_escaped) + { + flags &= ~ flag_escaped; + + switch (b) + { + case 'b': string_add ('\b'); break; + case 'f': string_add ('\f'); break; + case 'n': string_add ('\n'); break; + case 'r': string_add ('\r'); break; + case 't': string_add ('\t'); break; + case 'u': + + if (end - state.ptr <= 4 || + (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) + { + sprintf (error, "%u:%u: Invalid character value `%c`", line_and_col, b); + goto e_failed; + } + + uc_b1 = (uc_b1 << 4) | uc_b2; + uc_b2 = (uc_b3 << 4) | uc_b4; + uchar = (uc_b1 << 8) | uc_b2; + + if ((uchar & 0xF800) == 0xD800) { + json_uchar uchar2; + + if (end - state.ptr <= 6 || (*++ state.ptr) != '\\' || (*++ state.ptr) != 'u' || + (uc_b1 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b2 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b3 = hex_value (*++ state.ptr)) == 0xFF || + (uc_b4 = hex_value (*++ state.ptr)) == 0xFF) + { + sprintf (error, "%u:%u: Invalid character value `%c`", line_and_col, b); + goto e_failed; + } + + uc_b1 = (uc_b1 << 4) | uc_b2; + uc_b2 = (uc_b3 << 4) | uc_b4; + uchar2 = (uc_b1 << 8) | uc_b2; + + uchar = 0x010000 | ((uchar & 0x3FF) << 10) | (uchar2 & 0x3FF); + } + + if (sizeof (json_char) >= sizeof (json_uchar) || (uchar <= 0x7F)) + { + string_add ((json_char) uchar); + break; + } + + if (uchar <= 0x7FF) + { + if (state.first_pass) + string_length += 2; + else + { string [string_length ++] = 0xC0 | (uchar >> 6); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + } + + if (uchar <= 0xFFFF) { + if (state.first_pass) + string_length += 3; + else + { string [string_length ++] = 0xE0 | (uchar >> 12); + string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + } + + if (state.first_pass) + string_length += 4; + else + { string [string_length ++] = 0xF0 | (uchar >> 18); + string [string_length ++] = 0x80 | ((uchar >> 12) & 0x3F); + string [string_length ++] = 0x80 | ((uchar >> 6) & 0x3F); + string [string_length ++] = 0x80 | (uchar & 0x3F); + } + + break; + + default: + string_add (b); + }; + + continue; + } + + if (b == '\\') + { + flags |= flag_escaped; + continue; + } + + if (b == '"') + { + if (!state.first_pass) + string [string_length] = 0; + + flags &= ~ flag_string; + string = 0; + + switch (top->type) + { + case json_string: + + top->u.string.length = string_length; + flags |= flag_next; + + break; + + case json_object: + + if (state.first_pass) { + json_char **chars = (json_char **) &top->u.object.values; + chars[0] += string_length + 1; + } + else + { + top->u.object.values [top->u.object.length].name + = (json_char *) top->_reserved.object_mem; + + top->u.object.values [top->u.object.length].name_length + = string_length; + + (*(json_char **) &top->_reserved.object_mem) += string_length + 1; + } + + flags |= flag_seek_value | flag_need_colon; + continue; + + default: + break; + }; + } + else + { + string_add (b); + continue; + } + } + + if (state.settings.settings & json_enable_comments) + { + if (flags & (flag_line_comment | flag_block_comment)) + { + if (flags & flag_line_comment) + { + if (b == '\r' || b == '\n' || !b) + { + flags &= ~ flag_line_comment; + -- state.ptr; /* so null can be reproc'd */ + } + + continue; + } + + if (flags & flag_block_comment) + { + if (!b) + { sprintf (error, "%u:%u: Unexpected EOF in block comment", line_and_col); + goto e_failed; + } + + if (b == '*' && state.ptr < (end - 1) && state.ptr [1] == '/') + { + flags &= ~ flag_block_comment; + ++ state.ptr; /* skip closing sequence */ + } + + continue; + } + } + else if (b == '/') + { + if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object) + { sprintf (error, "%u:%u: Comment not allowed here", line_and_col); + goto e_failed; + } + + if (++ state.ptr == end) + { sprintf (error, "%u:%u: EOF unexpected", line_and_col); + goto e_failed; + } + + switch (b = *state.ptr) + { + case '/': + flags |= flag_line_comment; + continue; + + case '*': + flags |= flag_block_comment; + continue; + + default: + sprintf (error, "%u:%u: Unexpected `%c` in comment opening sequence", line_and_col, b); + goto e_failed; + }; + } + } + + if (flags & flag_done) + { + if (!b) + break; + + switch (b) + { + whitespace: + continue; + + default: + + sprintf (error, "%u:%u: Trailing garbage: `%c`", + line_and_col, b); + + goto e_failed; + }; + } + + if (flags & flag_seek_value) + { + switch (b) + { + whitespace: + continue; + + case ']': + + if (top && top->type == json_array) + flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; + else + { sprintf (error, "%u:%u: Unexpected `]`", line_and_col); + goto e_failed; + } + + break; + + default: + + if (flags & flag_need_comma) + { + if (b == ',') + { flags &= ~ flag_need_comma; + continue; + } + else + { + sprintf (error, "%u:%u: Expected `,` before `%c`", + line_and_col, b); + + goto e_failed; + } + } + + if (flags & flag_need_colon) + { + if (b == ':') + { flags &= ~ flag_need_colon; + continue; + } + else + { + sprintf (error, "%u:%u: Expected `:` before `%c`", + line_and_col, b); + + goto e_failed; + } + } + + flags &= ~ flag_seek_value; + + switch (b) + { + case '{': + + if (!new_value (&state, &top, &root, &alloc, json_object)) + goto e_alloc_failure; + + continue; + + case '[': + + if (!new_value (&state, &top, &root, &alloc, json_array)) + goto e_alloc_failure; + + flags |= flag_seek_value; + continue; + + case '"': + + if (!new_value (&state, &top, &root, &alloc, json_string)) + goto e_alloc_failure; + + flags |= flag_string; + + string = top->u.string.ptr; + string_length = 0; + + continue; + + case 't': + + if ((end - state.ptr) <= 3 || *(++ state.ptr) != 'r' || + *(++ state.ptr) != 'u' || *(++ state.ptr) != 'e') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + top->u.boolean = 1; + + flags |= flag_next; + break; + + case 'f': + + if ((end - state.ptr) <= 4 || *(++ state.ptr) != 'a' || + *(++ state.ptr) != 'l' || *(++ state.ptr) != 's' || + *(++ state.ptr) != 'e') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + case 'n': + + if ((end - state.ptr) <= 3 || *(++ state.ptr) != 'u' || + *(++ state.ptr) != 'l' || *(++ state.ptr) != 'l') + { + goto e_unknown_value; + } + + if (!new_value (&state, &top, &root, &alloc, json_null)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + default: + + if (isdigit ((unsigned char) b) || b == '-') + { + if (!new_value (&state, &top, &root, &alloc, json_integer)) + goto e_alloc_failure; + + if (!state.first_pass) + { + while (isdigit ((unsigned char) b) || b == '+' || b == '-' + || b == 'e' || b == 'E' || b == '.') + { + if ( (++ state.ptr) == end) + { + b = 0; + break; + } + + b = *state.ptr; + } + + flags |= flag_next | flag_reproc; + break; + } + + flags &= ~ (flag_num_negative | flag_num_e | + flag_num_e_got_sign | flag_num_e_negative | + flag_num_zero); + + num_digits = 0; + num_fraction = 0; + num_e = 0; + + if (b != '-') + { + flags |= flag_reproc; + break; + } + + flags |= flag_num_negative; + continue; + } + else + { sprintf (error, "%u:%u: Unexpected `%c` when seeking value", line_and_col, b); + goto e_failed; + } + }; + }; + } + else + { + switch (top->type) + { + case json_object: + + switch (b) + { + whitespace: + continue; + + case '"': + + if (flags & flag_need_comma) + { sprintf (error, "%u:%u: Expected `,` before `\"`", line_and_col); + goto e_failed; + } + + flags |= flag_string; + + string = (json_char *) top->_reserved.object_mem; + string_length = 0; + + break; + + case '}': + + flags = (flags & ~ flag_need_comma) | flag_next; + break; + + case ',': + + if (flags & flag_need_comma) + { + flags &= ~ flag_need_comma; + break; + } /* FALLTHRU */ + + default: + sprintf (error, "%u:%u: Unexpected `%c` in object", line_and_col, b); + goto e_failed; + }; + + break; + + case json_integer: + case json_double: + + if (isdigit ((unsigned char)b)) + { + ++ num_digits; + + if (top->type == json_integer || flags & flag_num_e) + { + if (! (flags & flag_num_e)) + { + if (flags & flag_num_zero) + { sprintf (error, "%u:%u: Unexpected `0` before `%c`", line_and_col, b); + goto e_failed; + } + + if (num_digits == 1 && b == '0') + flags |= flag_num_zero; + } + else + { + flags |= flag_num_e_got_sign; + num_e = (num_e * 10) + (b - '0'); + continue; + } + + if (would_overflow(top->u.integer, b)) + { + json_int_t integer = top->u.integer; + -- num_digits; + -- state.ptr; + top->type = json_double; + top->u.dbl = (double)integer; + continue; + } + + top->u.integer = (top->u.integer * 10) + (b - '0'); + continue; + } + + if (flags & flag_num_got_decimal) + num_fraction = (num_fraction * 10) + (b - '0'); + else + top->u.dbl = (top->u.dbl * 10) + (b - '0'); + + continue; + } + + if (b == '+' || b == '-') + { + if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign)) + { + flags |= flag_num_e_got_sign; + + if (b == '-') + flags |= flag_num_e_negative; + + continue; + } + } + else if (b == '.' && top->type == json_integer) + { + json_int_t integer = top->u.integer; + + if (!num_digits) + { sprintf (error, "%u:%u: Expected digit before `.`", line_and_col); + goto e_failed; + } + + top->type = json_double; + top->u.dbl = (double) integer; + + flags |= flag_num_got_decimal; + num_digits = 0; + continue; + } + + if (! (flags & flag_num_e)) + { + if (top->type == json_double) + { + if (!num_digits) + { sprintf (error, "%u:%u: Expected digit after `.`", line_and_col); + goto e_failed; + } + + top->u.dbl += num_fraction / pow (10.0, num_digits); + } + + if (b == 'e' || b == 'E') + { + flags |= flag_num_e; + + if (top->type == json_integer) + { + json_int_t integer = top->u.integer; + top->type = json_double; + top->u.dbl = (double) integer; + } + + num_digits = 0; + flags &= ~ flag_num_zero; + + continue; + } + } + else + { + if (!num_digits) + { sprintf (error, "%u:%u: Expected digit after `e`", line_and_col); + goto e_failed; + } + + top->u.dbl *= pow (10.0, (flags & flag_num_e_negative ? - num_e : num_e)); + } + + if (flags & flag_num_negative) + { + if (top->type == json_integer) + top->u.integer = - top->u.integer; + else + top->u.dbl = - top->u.dbl; + } + + flags |= flag_next | flag_reproc; + break; + + default: + break; + }; + } + + if (flags & flag_reproc) + { + flags &= ~ flag_reproc; + -- state.ptr; + } + + if (flags & flag_next) + { + flags = (flags & ~ flag_next) | flag_need_comma; + + if (!top->parent) + { + /* root value done */ + + flags |= flag_done; + continue; + } + + if (top->parent->type == json_array) + flags |= flag_seek_value; + + if (!state.first_pass) + { + json_value * parent = top->parent; + + switch (parent->type) + { + case json_object: + + parent->u.object.values + [parent->u.object.length].value = top; + + break; + + case json_array: + + parent->u.array.values + [parent->u.array.length] = top; + + break; + + default: + break; + }; + } + + if ( (++ top->parent->u.array.length) > UINT_MAX - 8) + goto e_overflow; + + top = top->parent; + + continue; + } + } + + alloc = root; + } + + return root; + +e_unknown_value: + + sprintf (error, "%u:%u: Unknown value", line_and_col); + goto e_failed; + +e_alloc_failure: + + strcpy (error, "Memory allocation failure"); + goto e_failed; + +e_overflow: + + sprintf (error, "%u:%u: Too long (caught overflow)", line_and_col); + goto e_failed; + +e_failed: + + if (error_buf) + { + if (*error) + strcpy (error_buf, error); + else + strcpy (error_buf, "Unknown error"); + } + + if (state.first_pass) + alloc = root; + + while (alloc) + { + top = alloc->_reserved.next_alloc; + state.settings.mem_free (alloc, state.settings.user_data); + alloc = top; + } + + if (!state.first_pass) + json_value_free_ex (&state.settings, root); + + return 0; +} + +__attribute__((visibility ("hidden"))) +json_value * json_parse (const json_char * json, size_t length) +{ + json_settings settings = { 0 }; + return json_parse_ex (&settings, json, length, 0); +} + +__attribute__((visibility ("hidden"))) +void json_value_free_ex (json_settings * settings, json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + settings->mem_free (value->u.array.values, settings->user_data); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + settings->mem_free (value->u.object.values, settings->user_data); + break; + } + + value = value->u.object.values [-- value->u.object.length].value; + continue; + + case json_string: + + settings->mem_free (value->u.string.ptr, settings->user_data); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + settings->mem_free (cur_value, settings->user_data); + } +} + +__attribute__((visibility ("hidden"))) +void json_value_free (json_value * value) +{ + json_settings settings = { 0 }; + settings.mem_free = default_free; + json_value_free_ex (&settings, value); +} diff --git a/opal/util/json/3rd-party/json.h b/opal/util/json/3rd-party/json.h new file mode 100644 index 00000000000..1f6431614c1 --- /dev/null +++ b/opal/util/json/3rd-party/json.h @@ -0,0 +1,288 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2012-2021 the json-parser authors All rights reserved. + * https://github.com/json-parser/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _JSON_H +#define _JSON_H + +#ifndef json_char + #define json_char char +#endif + +#ifndef json_int_t + #undef JSON_INT_T_OVERRIDDEN + #if defined(_MSC_VER) + #define json_int_t __int64 + #elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__cplusplus) && __cplusplus >= 201103L) + /* C99 and C++11 */ + #include + #define json_int_t int_fast64_t + #else + /* C89 */ + #define json_int_t long + #endif +#else + #define JSON_INT_T_OVERRIDDEN 1 +#endif + +#include + +#ifdef __cplusplus + + #include + + extern "C" + { + +#endif + +typedef struct +{ + unsigned long max_memory; /* should be size_t, but would modify the API */ + int settings; + + /* Custom allocator support (leave null to use malloc/free) + */ + + void * (* mem_alloc) (size_t, int zero, void * user_data); + void (* mem_free) (void *, void * user_data); + + void * user_data; /* will be passed to mem_alloc and mem_free */ + + size_t value_extra; /* how much extra space to allocate for values? */ + +} json_settings; + +#define json_enable_comments 0x01 + +typedef enum +{ + json_none, + json_object, + json_array, + json_integer, + json_double, + json_string, + json_boolean, + json_null + +} json_type; + +extern const struct _json_value json_value_none; + +typedef struct _json_object_entry +{ + json_char * name; + unsigned int name_length; + + struct _json_value * value; + +} json_object_entry; + +typedef struct _json_value +{ + struct _json_value * parent; + + json_type type; + + union + { + int boolean; + json_int_t integer; + double dbl; + + struct + { + unsigned int length; + json_char * ptr; /* null terminated */ + + } string; + + struct + { + unsigned int length; + + json_object_entry * values; + + #if defined(__cplusplus) + json_object_entry * begin () const + { return values; + } + json_object_entry * end () const + { return values + length; + } + #endif + + } object; + + struct + { + unsigned int length; + struct _json_value ** values; + + #if defined(__cplusplus) + _json_value ** begin () const + { return values; + } + _json_value ** end () const + { return values + length; + } + #endif + + } array; + + } u; + + union + { + struct _json_value * next_alloc; + void * object_mem; + + } _reserved; + + #ifdef JSON_TRACK_SOURCE + + /* Location of the value in the source JSON + */ + unsigned int line, col; + + #endif + + + /* Some C++ operator sugar */ + + #ifdef __cplusplus + + public: + + inline _json_value () + { memset (this, 0, sizeof (_json_value)); + } + + inline const struct _json_value &operator [] (int index) const + { + if (type != json_array || index < 0 + || ((unsigned int) index) >= u.array.length) + { + return json_value_none; + } + + return *u.array.values [index]; + } + + inline const struct _json_value &operator [] (const char * index) const + { + if (type != json_object) + return json_value_none; + + for (unsigned int i = 0; i < u.object.length; ++ i) + if (!strcmp (u.object.values [i].name, index)) + return *u.object.values [i].value; + + return json_value_none; + } + + inline operator const char * () const + { + switch (type) + { + case json_string: + return u.string.ptr; + + default: + return ""; + }; + } + + inline operator json_int_t () const + { + switch (type) + { + case json_integer: + return u.integer; + + case json_double: + return (json_int_t) u.dbl; + + default: + return 0; + }; + } + + inline operator bool () const + { + if (type != json_boolean) + return false; + + return u.boolean != 0; + } + + inline operator double () const + { + switch (type) + { + case json_integer: + return (double) u.integer; + + case json_double: + return u.dbl; + + default: + return 0; + }; + } + + #endif + +} json_value; + +json_value * json_parse (const json_char * json, + size_t length); + +#define json_error_max 128 +json_value * json_parse_ex (json_settings * settings, + const json_char * json, + size_t length, + char * error); + +void json_value_free (json_value *); + + +/* Not usually necessary, unless you used a custom mem_alloc and now want to + * use a custom mem_free. + */ +void json_value_free_ex (json_settings * settings, + json_value *); + + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif diff --git a/opal/util/json/Makefile.am b/opal/util/json/Makefile.am new file mode 100644 index 00000000000..75cc978eea5 --- /dev/null +++ b/opal/util/json/Makefile.am @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Amazon.com, Inc. or its affiliates. +# All Rights reserved. +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# + +noinst_LTLIBRARIES = libopalutil_json.la + +libopalutil_json_la_SOURCES = \ + 3rd-party/json.h \ + 3rd-party/json.c \ + opal_json.h \ + opal_json.c diff --git a/opal/util/json/help-json.txt b/opal/util/json/help-json.txt new file mode 100644 index 00000000000..e6adb5e8bc5 --- /dev/null +++ b/opal/util/json/help-json.txt @@ -0,0 +1,28 @@ +# -*- text -*- +# +# Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +[Unknown JSON value type] +The JSON value has an unknown type %s. + +[Invalid argument type] +Function %s expects input type %s(%d) but got %d. + +[Invalid JSON string] +The input is not a valid JSON string. + +[Unable to open file] +Failed to open file %s. Please check the file path and permissions. + +[Memory allocation failure] +Cannot allocate memory. + +[Unable to read file] +Encountered error reading file %s. + +[Index out of bound] +Index(%zu) is out of bound(length %ud). diff --git a/opal/util/json/opal_json.c b/opal/util/json/opal_json.c new file mode 100644 index 00000000000..57109b369a4 --- /dev/null +++ b/opal/util/json/opal_json.c @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. + * All Rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#include "opal/util/json/opal_json.h" +#include "opal/constants.h" +#include "opal/util/json/3rd-party/json.h" +#include "opal/util/show_help.h" +#include +#include +#include + +#define CHECK_OBJ_TYPE(obj, expected) \ + do { \ + if ((obj)->type != (expected)) { \ + opal_show_help("help-json.txt", "Invalid argument type", true, __func__, #expected, \ + expected, (obj)->type); \ + return OPAL_ERROR; \ + } \ + } while (0) + +struct opal_json_internal_t { + struct opal_json_t parent; + json_value *value; +}; +typedef struct opal_json_internal_t opal_json_internal_t; + +static void opal_json_internal_free(opal_json_internal_t *json) +{ + if (NULL == json) { + return; + } + + /* The root JSON object will release the memory of its children */ + if (json->value && NULL == json->value->parent) { + json_value_free(json->value); + } + + free(json); +} + +static inline int opal_json_internal_translate_type(json_type type, opal_json_type *out) +{ + int ret = OPAL_SUCCESS; + switch (type) { + case json_null: + *out = OPAL_JSON_NULL; + break; + case json_array: + *out = OPAL_JSON_ARRAY; + break; + case json_boolean: + *out = OPAL_JSON_BOOL; + break; + case json_integer: + *out = OPAL_JSON_INTEGER; + break; + case json_double: + *out = OPAL_JSON_DOUBLE; + break; + case json_string: + *out = OPAL_JSON_STRING; + break; + case json_object: + *out = OPAL_JSON_OBJECT; + break; + default: + opal_show_help("help-json.txt", "Unknown JSON value type", true, type); + *out = OPAL_JSON_TYPE_COUNT; + ret = OPAL_ERROR; + } + + return ret; +} + +static int opal_json_internal_new(const json_value *in, opal_json_internal_t **out) +{ + *out = malloc(sizeof(opal_json_internal_t)); + if (NULL == *out) { + return OPAL_ERROR; + } + + (*out)->value = (json_value *) in; + return opal_json_internal_translate_type(in->type, &(*out)->parent.type); +} + +int opal_json_load(const char *str, const size_t len, const opal_json_t **json) +{ + opal_json_internal_t *out = NULL; + int ret = OPAL_SUCCESS; + + json_value *value = json_parse(str, len); + if (!value) { + opal_show_help("help-json.txt", "Invalid JSON string", true); + ret = OPAL_ERROR; + goto out; + } + + ret = opal_json_internal_new(value, &out); + +out: + if (OPAL_SUCCESS == ret) { + *json = (opal_json_t *) out; + } else if (out) { + opal_json_internal_free(out); + } + + return ret; +} + +int opal_json_load_file(const char *filename, const opal_json_t **json) +{ + FILE *fp = NULL; + size_t file_size; + char *file_contents = NULL; + int ret = OPAL_SUCCESS; + + fp = fopen(filename, "r"); + if (fp == NULL) { + opal_show_help("help-json.txt", "Unable to open file", true, filename); + ret = OPAL_ERROR; + goto out; + } + + (void) fseek(fp, 0L, SEEK_END); + file_size = ftell(fp); + rewind(fp); + + file_contents = (char *) malloc(file_size); + if (!file_contents) { + opal_show_help("help-json.txt", "Memory allocation failure", true); + ret = OPAL_ERROR; + goto out; + } + + if (file_size > fread(file_contents, 1, file_size, fp)) { + opal_show_help("help-json.txt", "Unable to read file", true, filename); + ret = OPAL_ERROR; + goto out; + } + + ret = opal_json_load(file_contents, file_size, json); + +out: + if (fp) { + fclose(fp); + } + if (file_contents) { + free(file_contents); + } + + return ret; +} + +int opal_json_get_key(const opal_json_t *json, const char *key, const opal_json_t **out) +{ + int ret = OPAL_ERROR; + + CHECK_OBJ_TYPE(json, OPAL_JSON_OBJECT); + + opal_json_internal_t *in = (opal_json_internal_t *) json; + opal_json_internal_t *result = NULL; + + json_object_entry entry = {0}; + + for (unsigned int i = 0; i < in->value->u.object.length; ++i) { + entry = in->value->u.object.values[i]; + if (0 == strcmp(entry.name, key)) { + ret = opal_json_internal_new(entry.value, &result); + break; + } + } + + if (OPAL_SUCCESS == ret) { + *out = (opal_json_t *) result; + } else if (result) { + opal_json_internal_free(result); + } + + return ret; +} + +void opal_json_free(const opal_json_t **json) +{ + opal_json_internal_free((struct opal_json_internal_t *) *json); +} + +int opal_json_get_index(const opal_json_t *json, const size_t index, const opal_json_t **out) +{ + int ret = OPAL_ERROR; + + CHECK_OBJ_TYPE(json, OPAL_JSON_ARRAY); + + opal_json_internal_t *in = (opal_json_internal_t *) json; + opal_json_internal_t *result = NULL; + + if ((size_t) in->value->u.array.length > index) { + ret = opal_json_internal_new(in->value->u.array.values[index], &result); + } else { + opal_show_help("help-json.txt", "Index out of bound", true, index, + in->value->u.array.length); + } + + if (OPAL_SUCCESS == ret) { + *out = (opal_json_t *) result; + } else if (result) { + opal_json_internal_free(result); + } + + return ret; +} + +int opal_json_get_container_size(const opal_json_t *json, size_t *len) +{ + int ret = OPAL_ERROR; + if (OPAL_JSON_OBJECT == json->type) { + *len = (size_t) ((opal_json_internal_t *) json)->value->u.object.length; + ret = OPAL_SUCCESS; + } else if (OPAL_JSON_ARRAY == json->type) { + *len = (size_t) ((opal_json_internal_t *) json)->value->u.array.length; + ret = OPAL_SUCCESS; + } + return ret; +} + +int opal_json_read_integer(const opal_json_t *json, int64_t *out) +{ + CHECK_OBJ_TYPE(json, OPAL_JSON_INTEGER); + opal_json_internal_t *in = (opal_json_internal_t *) json; + *out = in->value->u.integer; + return OPAL_SUCCESS; +} + +int opal_json_read_double(const opal_json_t *json, double *out) +{ + CHECK_OBJ_TYPE(json, OPAL_JSON_DOUBLE); + opal_json_internal_t *in = (opal_json_internal_t *) json; + *out = in->value->u.dbl; + return OPAL_SUCCESS; +} + +int opal_json_read_bool(const opal_json_t *json, bool *out) +{ + CHECK_OBJ_TYPE(json, OPAL_JSON_BOOL); + opal_json_internal_t *in = (opal_json_internal_t *) json; + *out = in->value->u.boolean; + return OPAL_SUCCESS; +} + +int opal_json_read_string(const opal_json_t *json, const char **out, size_t *len) +{ + CHECK_OBJ_TYPE(json, OPAL_JSON_STRING); + opal_json_internal_t *in = (opal_json_internal_t *) json; + *out = in->value->u.string.ptr; + *len = in->value->u.string.length; + return OPAL_SUCCESS; +} diff --git a/opal/util/json/opal_json.h b/opal/util/json/opal_json.h new file mode 100644 index 00000000000..a29642e6a9c --- /dev/null +++ b/opal/util/json/opal_json.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. + * All Rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +/** @file: + * + * Utility data structures and functions to parse JSON from a string or file. + * Note: The internal 3rd-party code should NOT be directly used. Implement the + * functionality here if necessary. + * + * Below is a trivial example to parse a JSON file with the content {"key": "val"}. + +int ret = OPAL_SUCCESS; +const opal_json_t *json = NULL, *item = NULL; +char *val; +size_t len; + +ret = opal_json_load_file("my_file.json", &json); // Parse the file content into json +if (OPAL_SUCCESS != ret) { + goto out; +} + +ret = opal_json_get_key(json, "key", &item); // Get the item pointed to by "key" +if (OPAL_SUCCESS != ret) { + goto out; +} + +ret = opal_json_read_string(item, (const char **) &val, &len); // Read the string value and length +if (OPAL_SUCCESS != ret) { + goto out; +} + +out: + +// Cleanup +opal_json_free(&json); +opal_json_free(&item); + + */ + +#ifndef OPAL_JSON_H +#define OPAL_JSON_H + +#include "opal/mca/mca.h" + +BEGIN_C_DECLS + +typedef enum { + OPAL_JSON_NULL, + OPAL_JSON_OBJECT, + OPAL_JSON_ARRAY, + OPAL_JSON_INTEGER, + OPAL_JSON_DOUBLE, + OPAL_JSON_BOOL, + OPAL_JSON_STRING, + OPAL_JSON_TYPE_COUNT, +} opal_json_type; + +struct opal_json_t { + opal_json_type type; +}; +typedef struct opal_json_t opal_json_t; + +/** + * Load JSON from a string. + * + * @param[in] str A JSON string + * @param[in] len First bytes to parse + * @param[out] json Output JSON object if the string is valid + * + * @returns OPAL_SUCCESS if the string is parsed successfully + * OPAL_ERROR otherwise + */ +OPAL_DECLSPEC int opal_json_load(const char *str, const size_t len, const opal_json_t **json); + +/** + * Load JSON from a file. + * + * @param[in] filename File name + * @param[out] json Output JSON object if the file valid + * + * @returns OPAL_SUCCESS if the file is read and parsed successfully + * OPAL_ERROR otherwise + */ +OPAL_DECLSPEC int opal_json_load_file(const char *filename, const opal_json_t **json); + +/** + * Free JSON resources + * + * @param[in] json Pointer to JSON object to free + */ +OPAL_DECLSPEC void opal_json_free(const opal_json_t **json); + +/** + * Get the JSON object with a key value from a parent object + * + * @param[in] json Parent JSON object + * @param[in] key Key value + * @param[out] out Output JSON object with the specified key value + * + * @returns OPAL_SUCCESS if an object is found with the specified key value + * OPAL_ERROR otherwise + */ +OPAL_DECLSPEC int opal_json_get_key(const opal_json_t *json, const char *key, + const opal_json_t **out); + +/** + * Get the JSON object at index from a JSON array + * + * @param[in] json Parent JSON array + * @param[in] index Index value + * @param[out] out Output JSON object at the specified index + * + * @returns OPAL_SUCCESS if an object is found at the specified index + * OPAL_ERROR otherwise + */ +OPAL_DECLSPEC int opal_json_get_index(const opal_json_t *json, const size_t index, + const opal_json_t **out); + +/** + * Get the number of objects in a container-type value, i.e. object, array. + * + * @param[in] json A container JSON object + * @param[out] len Number of elements in the container + * + * @returns OPAL_SUCCESS if successful + * OPAL_ERROR otherwise + */ +OPAL_DECLSPEC int opal_json_get_container_size(const opal_json_t *json, size_t *len); + +/** + * Value reader functions + * + * The caller is responsible for ensuring the function be called for the correct the object type, + * i.e. *read_integer should only be called for integer objects. + * + * @returns OPAL_SUCCESS if successful + * OPAL_ERROR otherwise + */ + +OPAL_DECLSPEC int opal_json_read_bool(const opal_json_t *json, bool *out); +OPAL_DECLSPEC int opal_json_read_integer(const opal_json_t *json, int64_t *out); +OPAL_DECLSPEC int opal_json_read_double(const opal_json_t *json, double *out); +OPAL_DECLSPEC int opal_json_read_string(const opal_json_t *json, const char **out, size_t *len); + +END_C_DECLS + +#endif diff --git a/test/support/support.c b/test/support/support.c index 0bf45e085aa..cbf9fca6d94 100644 --- a/test/support/support.c +++ b/test/support/support.c @@ -10,6 +10,8 @@ * Copyright (c) 2004-2005 The Regents of the University of California. * All rights reserved. * Copyright (c) 2010 Cisco Systems, Inc. All rights reserved. + * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. + * All Rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -25,6 +27,28 @@ #include "support.h" +#define test_verify_number(T, specifier) \ + int test_verify_##T(T expected_result, T test_result) \ + { \ + int return_value; \ + return_value = 1; \ + if (expected_result != test_result) { \ + test_failure("Comparison failure"); \ + fprintf(stderr, " Expected result: " specifier "\n", expected_result); \ + fprintf(stderr, " Test result: " specifier "\n", test_result); \ + fflush(stderr); \ + return_value = 0; \ + } else { \ + test_success(); \ + } \ + return return_value; \ + } + +test_verify_number(int, "%d") +test_verify_number(int64_t, "%ld") +test_verify_number(size_t, "%lu") +test_verify_number(double, "%lf") + /** * A testing support library to provide uniform reporting output */ @@ -93,24 +117,6 @@ int test_verify_str(const char *expected_result, const char *test_result) return return_value; } -int test_verify_int(int expected_result, int test_result) -{ - int return_value; - - return_value = 1; - if (expected_result != test_result) { - test_failure("Comparison failure"); - fprintf(stderr, " Expected result: %d\n", expected_result); - fprintf(stderr, " Test result: %d\n", test_result); - fflush(stderr); - return_value = 0; - } else { - test_success(); - } - - return return_value; -} - int test_finalize(void) { int return_value; diff --git a/test/support/support.h b/test/support/support.h index 7b4a9491bf2..35c1a2ce97d 100644 --- a/test/support/support.h +++ b/test/support/support.h @@ -9,6 +9,8 @@ * University of Stuttgart. All rights reserved. * Copyright (c) 2004-2005 The Regents of the University of California. * All rights reserved. + * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. + * All Rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -33,6 +35,9 @@ void test_success(void); void test_failure(const char *a); int test_verify_str(const char *expected_result, const char *test_result); int test_verify_int(int expected_result, int test_result); +int test_verify_int64_t(int64_t expected_result, int64_t test_result); +int test_verify_size_t(size_t expected_result, size_t test_result); +int test_verify_double(double expected_result, double test_result); int test_finalize(void); void test_comment(const char *userstr); void test_fail_stop(const char *msg, int status); diff --git a/test/util/Makefile.am b/test/util/Makefile.am index e5ad4724478..99c1a4b224a 100644 --- a/test/util/Makefile.am +++ b/test/util/Makefile.am @@ -15,6 +15,8 @@ # Copyright (c) 2018 Research Organization for Information Science # and Technology (RIST). All rights reserved. # Copyright (c) 2018 Intel, Inc. All rights reserved. +# Copyright (c) 2024 Amazon.com, Inc. or its affiliates. +# All Rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -37,9 +39,10 @@ AM_CPPFLAGS = -I$(top_srcdir)/test/support check_PROGRAMS = \ + bipartite_graph \ opal_bit_ops \ opal_path_nfs \ - bipartite_graph \ + opal_json \ opal_sha256 TESTS = \ @@ -129,6 +132,12 @@ bipartite_graph_LDADD = \ $(top_builddir)/test/support/libsupport.a bipartite_graph_DEPENDENCIES = $(bipartite_graph_LDADD) +opal_json_SOURCES = opal_json.c +opal_json_LDADD = \ + $(top_builddir)/opal/lib@OPAL_LIB_NAME@.la \ + $(top_builddir)/test/support/libsupport.a +opal_json_DEPENDENCIES = $(opal_json_LDADD) + opal_sha256_SOURCES = opal_sha256.c opal_sha256_LDADD = \ $(top_builddir)/opal/lib@OPAL_LIB_NAME@.la \ diff --git a/test/util/opal_json.c b/test/util/opal_json.c new file mode 100644 index 00000000000..13611b4ba2e --- /dev/null +++ b/test/util/opal_json.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. + * All Rights reserved. + * $COPYRIGHT$ + * + * Additional copyrights may follow + * + * $HEADER$ + */ + +#include "opal_config.h" +#include "opal/util/json/opal_json.h" +#include "opal/runtime/opal.h" +#include "support.h" +#include +#include + +enum INPUT_TYPE { + JSON_STRING, + JSON_FILE, + INPUT_TYPE_COUNT, +}; + +#define FREE_JSON(obj) \ + do { \ + opal_json_free(&(obj)); \ + obj = NULL; \ + } while (0) + +static int load_json(const char *string, enum INPUT_TYPE input_type, const opal_json_t **json) +{ + FILE *fp = NULL; + char filename[] = "XXXXXXXXXX"; + int ret = 0, fd = -1; + + switch (input_type) { + case JSON_STRING: + /* Load the input string */ + ret = opal_json_load(string, strlen(string), json); + break; + case JSON_FILE: + /* Write the input string to a temporary file */ + fd = mkstemp(filename); + if (fd < 0) { + test_failure("Failed to create JSON file"); + ret = 1; + break; + } + fp = fdopen(fd, "w"); + if (fp == NULL) { + test_failure("Failed to open JSON file"); + ret = 1; + break; + } + fputs(string, fp); + fclose(fp); + close(fd); + /* Load the input string from the temporary file */ + ret = opal_json_load_file(filename, json); + /* Remember to delete the file */ + (void) remove(filename); + break; + default: + test_failure("Unknown input type!"); + ret = 1; + } + return ret; +} + +static void test_json_integer_val(const opal_json_t *json, int64_t expected_value) +{ + int ret = 0; + int64_t val; + ret = opal_json_read_integer(json, &val); + if (ret) { + test_failure("Failed to read an integer value"); + } else if (test_verify_int64_t(expected_value, val)) { + test_success(); + } else { + test_failure("Got a wrong integer value"); + } +} + +static void test_json_double_val(const opal_json_t *json, double expected_value) +{ + int ret = 0; + double val; + ret = opal_json_read_double(json, &val); + if (ret) { + test_failure("Failed to read a double value"); + } else if (test_verify_double(expected_value, val)) { + test_success(); + } else { + test_failure("Got a wrong double value"); + } +} + +static void test_json_bool_val(const opal_json_t *json, bool expected_value) +{ + int ret = 0; + bool val; + ret = opal_json_read_bool(json, &val); + if (ret) { + test_failure("Failed to read a boolean value"); + } else if (test_verify_int(expected_value, val)) { + test_success(); + } else { + test_failure("Got a wrong boolean value"); + } +} + +static void test_json_string_val(const opal_json_t *json, const char *expected_value) +{ + int ret; + size_t len; + char *val = NULL; + ret = opal_json_read_string(json, (const char **) &val, &len); + if (ret) { + test_failure("Failed to read a string value"); + } else if (test_verify_str(expected_value, val) && test_verify_size_t(strlen(val), len)) { + test_success(); + } else { + test_failure("Got a wrong string value"); + } +} + +static void test_valid_json(void) +{ + int ret = 0; + size_t size; + const opal_json_t *json = NULL, *a = NULL, *b = NULL, *b0 = NULL, *b1 = NULL, *c = NULL, + *d = NULL; + /** + * Human readable form: + * { + * "a": 1, + * "b": [ + * 2, + * true + * ], + * "c": "this is a string", + * "d": 3.456 + * } + */ + static const char *s + = "{\"a\": 1, \"b\": [2, true], \"c\": \"this is a string\", \"d\": 3.456}"; + + for (int input_type = JSON_STRING; input_type < INPUT_TYPE_COUNT; ++input_type) { + ret = load_json(s, input_type, &json); + if (ret) { + test_failure("Failed to load JSON"); + continue; + } else { + test_success(); + } + + ret = opal_json_get_container_size(json, &size); + if (ret) { + test_failure("Failed to get object size"); + } else { + test_verify_size_t(4, size); + } + + ret = opal_json_get_key(json, "a", &a); + if (ret) { + test_failure("Failed to find a valid key"); + } else { + test_json_integer_val(a, 1); + } + + ret = opal_json_get_key(json, "b", &b); + if (ret) { + test_failure("Failed to find a valid key"); + } else { + ret = opal_json_get_container_size(b, &size); + if (ret) { + test_failure("Failed to get array length"); + } else { + test_verify_size_t(2, size); + } + + ret = opal_json_get_index(b, 0, &b0); + if (ret) { + test_failure("Failed to get value at an index"); + } else { + test_json_integer_val(b0, 2); + } + + ret = opal_json_get_index(b, 1, &b1); + if (ret) { + test_failure("Failed to get value at an index"); + } else { + test_json_bool_val(b1, true); + } + } + + ret = opal_json_get_key(json, "c", &c); + if (ret) { + test_failure("Failed to find a valid key"); + } else { + test_json_string_val(c, "this is a string"); + } + + ret = opal_json_get_key(json, "d", &d); + if (ret) { + test_failure("Failed to find a valid key"); + } else { + test_json_double_val(d, 3.456); + } + + /* JSON objects can be released in any order */ + FREE_JSON(a); + FREE_JSON(b); + FREE_JSON(b0); + FREE_JSON(b1); + FREE_JSON(c); + FREE_JSON(json); + } +} + +static void test_invalid_json_string(void) +{ + int ret = 0; + static const int cnt = 3; + char *test_cases[cnt]; + + test_cases[0] = "1,2,3"; + test_cases[1] = "[1,2,3,]"; + test_cases[2] = "{a: 1}"; + + for (int i = 0; i < 1; ++i) { + const opal_json_t *json = NULL; + char *test_case = test_cases[i]; + for (int input_type = JSON_STRING; input_type < INPUT_TYPE_COUNT; ++input_type) { + ret = load_json(test_case, input_type, &json); + if (ret) { + test_success(); + } else { + test_failure("Failed to return error for an invalid JSON string"); + } + FREE_JSON(json); + } + } +} + +int main(int argc, char **argv) +{ + + opal_init(&argc, &argv); + test_init("opal_util_json"); + test_valid_json(); + test_invalid_json_string(); + opal_finalize(); + return test_finalize(); +}