From 988ffd25ff26e86ec0cbd9657e152a80d391c70d Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 15 Apr 2026 12:57:01 +0000 Subject: [PATCH 1/5] tests: fix memory leak of error string in test-2 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano --- tests/test-2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test-2.c b/tests/test-2.c index 722da9df..540381b3 100644 --- a/tests/test-2.c +++ b/tests/test-2.c @@ -28,7 +28,9 @@ main () parser_error err; runtime_spec_schema_config_schema *container = runtime_spec_schema_config_schema_parse_file ("tests/data/config.nocwd.json", 0, &err); if (container != NULL) { + free (err); exit (4); } + free (err); exit (0); } From e788cca7ceeb7d0bac7e3da318596c1d934d9139 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 15 Apr 2026 12:33:57 +0000 Subject: [PATCH 2/5] validate: fix LibFuzzer build by removing HF_ITER and guarding main() Remove the honggfuzz HF_ITER code path that caused undefined reference errors when building with -DFUZZER. Wrap main() in #ifndef FUZZER so LibFuzzer can provide its own entry point. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano --- src/ocispec/validate.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/ocispec/validate.c b/src/ocispec/validate.c index 0b262db5..143bf62c 100644 --- a/src/ocispec/validate.c +++ b/src/ocispec/validate.c @@ -62,6 +62,7 @@ LLVMFuzzerTestOneInput (uint8_t *buf, size_t len) } #endif +#ifndef FUZZER int main (int argc, char *argv[]) { @@ -70,22 +71,6 @@ main (int argc, char *argv[]) const char *file = "config.json"; struct parser_context ctx; -#ifdef FUZZER - if (getenv ("VALIDATE_FUZZ")) - { - extern void HF_ITER (uint8_t** buf, size_t* len); - for (;;) - { - size_t len; - uint8_t *buf; - - HF_ITER (&buf, &len); - - LLVMFuzzerTestOneInput (buf, len); - } - } -#endif - if (argc > 1) file = argv[1]; @@ -105,3 +90,4 @@ main (int argc, char *argv[]) exit (EXIT_SUCCESS); } +#endif From 8b780fb667b4b3b87369c251d6101d198adac1bb Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Wed, 15 Apr 2026 08:35:02 +0000 Subject: [PATCH 3/5] Replace YAJL with yyjson for JSON parsing and generation Migrate the entire JSON backend from YAJL to yyjson. This is a big-bang replacement with no side-by-side support. Closes: https://github.com/containers/libocispec/issues/138 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano --- .github/workflows/test.yaml | 8 +- .gitmodules | 6 +- Makefile.am | 22 +- README.md | 2 +- autogen.sh | 2 - configure.ac | 31 +- ocispec.pc.in | 2 +- src/ocispec/generate.py | 7 + src/ocispec/headers.py | 62 +-- src/ocispec/json_api.py | 146 +++--- src/ocispec/json_common.c | 878 +++++++++++++++++++++++++----------- src/ocispec/json_common.h | 93 ++-- src/ocispec/read-file.c | 8 +- src/ocispec/sources.py | 277 ++++++------ yajl | 1 - yyjson | 1 + 16 files changed, 933 insertions(+), 613 deletions(-) delete mode 160000 yajl create mode 160000 yyjson diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 22172982..b2ca8d47 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,17 +38,17 @@ jobs: install: | apt-get update -q -y - apt-get install -q -y python3 automake libtool autotools-dev git make cmake pkg-config gcc wget xz-utils + apt-get install -q -y python3 automake libtool autotools-dev git make pkg-config gcc wget xz-utils run: | find $(pwd) -name '.git' -exec bash -c 'git config --global --add safe.directory ${0%/.git}' {} \; - ./autogen.sh --enable-embedded-yajl - ./configure --enable-embedded-yajl CFLAGS='-Wall -Wextra -Werror' + ./autogen.sh --enable-embedded-yyjson + ./configure --enable-embedded-yyjson CFLAGS='-Wall -Wextra -Werror' # Run make check first to catch test failures with accessible logs make -j $(nproc) make check || (cat test-suite.log 2>/dev/null; exit 1) # Now run distcheck for distribution validation - make -j $(nproc) distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-embedded-yajl" + make -j $(nproc) distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-embedded-yyjson" # check that the working dir is clean git describe --broken --dirty --all | grep -qv dirty make clean diff --git a/.gitmodules b/.gitmodules index 0381f810..3ae3d2db 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "image-spec"] path = image-spec url = https://github.com/opencontainers/image-spec -[submodule "yajl"] - path = yajl - url = https://github.com/containers/yajl.git +[submodule "yyjson"] + path = yyjson + url = https://github.com/ibireme/yyjson.git diff --git a/Makefile.am b/Makefile.am index 36799002..c2b3e7c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,11 +1,8 @@ -DIST_SUBDIRS = yajl -SUBDIRS = yajl - AM_CFLAGS = $(WARN_CFLAGS) -I$(top_srcdir)/src -I$(top_builddir)/src -if HAVE_EMBEDDED_YAJL -AM_CFLAGS += -I$(top_srcdir)/yajl/src/headers -endif HAVE_EMBEDDED_YAJL +if HAVE_EMBEDDED_YYJSON +AM_CFLAGS += -I$(top_srcdir)/yyjson/src +endif HAVE_EMBEDDED_YYJSON CLEANFILES = $(man_MANS) src/runtime_spec_stamp src/image_spec_stamp src/image_manifest_stamp src/basic-test_stamp @@ -157,6 +154,10 @@ libocispec_la_SOURCES = $(BUILT_SOURCES) \ src/ocispec/read-file.c \ src/ocispec/json_common.c +if HAVE_EMBEDDED_YYJSON +libocispec_la_SOURCES += yyjson/src/yyjson.c +endif + TMP_H_FILES = $(HEADER_FILES:.h=.h.tmp) TMP_C_FILES = $(SOURCE_FILES:.c=.c.tmp) @@ -164,10 +165,9 @@ CLEANFILES += $(HEADER_FILES) $(SOURCE_FILES) $(TMP_H_FILES) $(TMP_C_FILES) TESTS_LDADD = libocispec.la $(SELINUX_LIBS) -if HAVE_EMBEDDED_YAJL -TESTS_LDADD += yajl/libyajl.la +if HAVE_EMBEDDED_YYJSON else -TESTS_LDADD += $(YAJL_LIBS) +TESTS_LDADD += $(YYJSON_LIBS) endif libocispec_a_SOURCES = @@ -281,12 +281,12 @@ EXTRA_DIST = autogen.sh \ image-spec \ src/ocispec/json_common.h \ src/ocispec/json_common.c \ - src/yajl + yyjson/src/yyjson.h sync: (cd image-spec; git pull https://github.com/opencontainers/image-spec) (cd runtime-spec; git pull https://github.com/opencontainers/runtime-spec) - (cd yajl; git pull https://github.com/containers/yajl main) + generate: src/runtime_spec_stamp src/image_spec_stamp src/image_manifest_stamp src/basic-test_stamp diff --git a/README.md b/README.md index 17e15a7a..8f69eb96 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ from C, and generate json string from corresponding struct. The parser is generated directly from the JSON schema in the source repository. ## Installation -Expects [yajl](https://github.com/containers/yajl) to be installed and linkable. +Expects [yyjson](https://github.com/ibireme/yyjson) to be installed and linkable. ```sh $ ./autogen.sh $ ./configure diff --git a/autogen.sh b/autogen.sh index d8bbf0ed..7c25e77a 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,7 +1,5 @@ #!/bin/sh -(cd yajl; ./autogen.sh) - git submodule update --init --recursive test -n "$srcdir" || srcdir=`dirname "$0"` diff --git a/configure.ac b/configure.ac index 6125dcbe..c4418525 100644 --- a/configure.ac +++ b/configure.ac @@ -12,23 +12,26 @@ AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign tar-ustar no-dist-gzip dist-xz s AM_MAINTAINER_MODE([enable]) AM_SILENT_RULES([yes]) -AM_EXTRA_RECURSIVE_TARGETS([yajl]) -AC_CONFIG_SUBDIRS([yajl]) - -AC_ARG_ENABLE(embedded-yajl, -AS_HELP_STRING([--enable-embedded-yajl], [Statically link a modified yajl version]), +AC_ARG_ENABLE(embedded-yyjson, +AS_HELP_STRING([--enable-embedded-yyjson], [Statically link the embedded yyjson version]), [ case "${enableval}" in - yes) embedded_yajl=true ;; - no) embedded_yajl=false ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-embedded-yajl) ;; -esac],[embedded_yajl=false]) - -AM_CONDITIONAL([HAVE_EMBEDDED_YAJL], [test x"$embedded_yajl" = xtrue]) -AM_COND_IF([HAVE_EMBEDDED_YAJL], [], [ -AC_SEARCH_LIBS(yajl_tree_get, [yajl], [AC_DEFINE([HAVE_YAJL], 1, [Define if libyajl is available])], [AC_MSG_ERROR([*** libyajl headers not found])]) -PKG_CHECK_MODULES([YAJL], [yajl >= 2.0.0]) + yes) embedded_yyjson=true ;; + no) embedded_yyjson=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-embedded-yyjson) ;; +esac],[embedded_yyjson=auto]) + +if test x"$embedded_yyjson" != xtrue; then +PKG_CHECK_MODULES([YYJSON], [yyjson >= 0.8.0], [embedded_yyjson=false], [ + if test x"$embedded_yyjson" = xauto; then + AC_MSG_NOTICE([yyjson not found on the system, using the embedded version]) + embedded_yyjson=true + else + AC_MSG_ERROR([yyjson >= 0.8.0 not found and embedded yyjson is disabled]) + fi ]) +fi +AM_CONDITIONAL([HAVE_EMBEDDED_YYJSON], [test x"$embedded_yyjson" = xtrue]) # Optionally install the library. AC_ARG_ENABLE(libocispec-install, diff --git a/ocispec.pc.in b/ocispec.pc.in index 9255abde..501cfbb9 100644 --- a/ocispec.pc.in +++ b/ocispec.pc.in @@ -5,7 +5,7 @@ includedir=@includedir@ Name: @PACKAGE_NAME@ Description: A library for easily parsing [OCI runtime](https://github.com/opencontainers/runtime-spec) and [OCI image](https://github.com/opencontainers/image-spec) files. -Requires: yajl +Requires: yyjson Version: @PACKAGE_VERSION@ Libs: -L${libdir} -locispec Cflags: -I${includedir}/ocispec diff --git a/src/ocispec/generate.py b/src/ocispec/generate.py index b33517f8..29c30b0e 100755 --- a/src/ocispec/generate.py +++ b/src/ocispec/generate.py @@ -31,6 +31,8 @@ import sys import json import argparse +import subprocess +import shutil from collections import OrderedDict import helpers @@ -731,6 +733,11 @@ def reflection(schema_info, gen_ref): except: pass + clang_format = shutil.which('clang-format') + if clang_format: + for filepath in [schema_info.header.name, schema_info.source.name]: + subprocess.run([clang_format, '-i', filepath], check=False) + if gen_ref is True: if schema_info.refs: for reffile in schema_info.refs.values(): diff --git a/src/ocispec/headers.py b/src/ocispec/headers.py index 6efe2b0c..1e138ccf 100755 --- a/src/ocispec/headers.py +++ b/src/ocispec/headers.py @@ -38,7 +38,7 @@ def append_header_arr(obj, header, prefix): if not obj.subtypobj or obj.subtypname: return - header.append("typedef struct {\n") + header.append("typedef struct\n{\n") for i in obj.subtypobj: if i.typ == 'array': c_typ = helpers.get_prefixed_pointer(i.name, i.subtyp, prefix) or \ @@ -47,21 +47,21 @@ def append_header_arr(obj, header, prefix): c_typ = helpers.get_name_substr(i.name, prefix) if not helpers.is_compound_type(i.subtyp): - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{i.fixname};\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{i.fixname};\n") else: - header.append(f" {c_typ} **{i.fixname};\n") - header.append(f" size_t {i.fixname + '_len'};\n\n") + header.append(f" {c_typ} **{i.fixname};\n") + header.append(f" size_t {i.fixname + '_len'};\n\n") else: c_typ = helpers.get_prefixed_pointer(i.name, i.typ, prefix) or \ helpers.get_map_c_types(i.typ) - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}{i.fixname};\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}{i.fixname};\n") for i in obj.subtypobj: if helpers.is_numeric_type(i.typ) or i.typ == 'boolean': - header.append(f" unsigned int {i.fixname}_present : 1;\n") + header.append(f" unsigned int {i.fixname}_present : 1;\n") typename = helpers.get_name_substr(obj.name, prefix) - header.append(f"}}\n{typename};\n\n") + header.append(f"}} {typename};\n\n") header.append(f"void free_{typename} ({typename} *ptr);\n\n") - header.append(f"{typename} *make_{typename} ({json_api.VAL_TYPE} tree, const struct parser_context *ctx, parser_error *err);\n\n") + header.append(f"{typename} *make_{typename} ({json_api.VAL_TYPE}tree, const struct parser_context *ctx, parser_error *err);\n\n") def append_header_map_str_obj(obj, header, prefix): @@ -71,16 +71,16 @@ def append_header_map_str_obj(obj, header, prefix): History: 2019-06-17 ''' child = obj.children[0] - header.append("typedef struct {\n") - header.append(" char **keys;\n") + header.append("typedef struct\n{\n") + header.append(" char **keys;\n") if helpers.valid_basic_map_name(child.typ): c_typ = helpers.get_prefixed_pointer("", child.typ, "") elif child.subtypname: c_typ = child.subtypname + " *" else: c_typ = helpers.get_prefixed_pointer(child.name, child.typ, prefix) - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{child.fixname};\n") - header.append(" size_t len;\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{child.fixname};\n") + header.append(" size_t len;\n") def append_header_child_arr(child, header, prefix): @@ -105,16 +105,16 @@ def append_header_child_arr(child, header, prefix): dflag = "*" if helpers.valid_basic_map_name(child.subtyp): - header.append(f" {helpers.make_basic_map_name(child.subtyp)} **{child.fixname};\n") + header.append(f" {helpers.make_basic_map_name(child.subtyp)} **{child.fixname};\n") elif not helpers.is_compound_type(child.subtyp): - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{dflag}{child.fixname};\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*{dflag}{child.fixname};\n") else: - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}**{dflag}{child.fixname};\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}**{dflag}{child.fixname};\n") if child.nested_array and not helpers.valid_basic_map_name(child.subtyp): - header.append(f" size_t *{child.fixname + '_item_lens'};\n") + header.append(f" size_t *{child.fixname + '_item_lens'};\n") - header.append(f" size_t {child.fixname + '_len'};\n\n") + header.append(f" size_t {child.fixname + '_len'};\n\n") def append_header_child_others(child, header, prefix): ''' @@ -130,7 +130,7 @@ def append_header_child_others(child, header, prefix): c_typ = helpers.get_prefixed_pointer(child.subtypname, child.typ, "") else: c_typ = helpers.get_prefixed_pointer(child.name, child.typ, prefix) - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}{child.fixname};\n\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}{child.fixname};\n\n") def append_type_c_header(obj, header, prefix): @@ -153,29 +153,29 @@ def append_type_c_header(obj, header, prefix): elif obj.typ == 'object': if obj.subtypname is not None: return - header.append("typedef struct {\n") + header.append("typedef struct\n{\n") if obj.children is None: - header.append(" char unuseful; // unuseful definition to avoid empty struct\n") + header.append(" char unuseful; // unuseful definition to avoid empty struct\n") present_tags = [] for i in obj.children or []: if helpers.is_numeric_type(i.typ) or i.typ == 'boolean': - present_tags.append(f" unsigned int {i.fixname}_present : 1;\n") + present_tags.append(f" unsigned int {i.fixname}_present : 1;\n") if i.typ == 'array': append_header_child_arr(i, header, prefix) else: append_header_child_others(i, header, prefix) if obj.children is not None: - header.append(f" {json_api.VAL_TYPE} _residual;\n") + header.append(f" {json_api.RESIDUAL_TYPE}_residual;\n") if len(present_tags) > 0: header.append("\n") for tag in present_tags: header.append(tag) typename = helpers.get_prefixed_name(obj.name, prefix) - header.append(f"}}\n{typename};\n\n") + header.append(f"}} {typename};\n\n") header.append(f"void free_{typename} ({typename} *ptr);\n\n") header.append(f"{typename} *clone_{typename} ({typename} *src);\n") - header.append(f"{typename} *make_{typename} ({json_api.VAL_TYPE} tree, const struct parser_context *ctx, parser_error *err);\n\n") - header.append(f"{json_api.GEN_STATUS_TYPE} gen_{typename} ({json_api.GEN_TYPE} g, const {typename} *ptr, const struct parser_context *ctx, parser_error *err);\n\n") + header.append(f"{typename} *make_{typename} ({json_api.VAL_TYPE}tree, const struct parser_context *ctx, parser_error *err);\n\n") + header.append(f"{json_api.GEN_STATUS_TYPE} gen_{typename} ({json_api.GEN_TYPE}g, const {typename} *ptr, const struct parser_context *ctx, parser_error *err);\n\n") def header_reflect_top_array(obj, prefix, header): c_typ = helpers.get_prefixed_pointer(obj.name, obj.subtyp, prefix) or \ @@ -189,14 +189,14 @@ def header_reflect_top_array(obj, prefix, header): return typename = helpers.get_top_array_type_name(obj.name, prefix) - header.append("typedef struct {\n") + header.append("typedef struct\n{\n") if obj.nested_array: - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}**items;\n") - header.append(" size_t *subitem_lens;\n\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}**items;\n") + header.append(" size_t *subitem_lens;\n\n") else: - header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*items;\n") - header.append(" size_t len;\n\n") - header.append(f"}}\n{typename};\n\n") + header.append(f" {c_typ}{' ' if '*' not in c_typ else ''}*items;\n") + header.append(" size_t len;\n\n") + header.append(f"}} {typename};\n\n") header.append(f"void free_{typename} ({typename} *ptr);\n\n") diff --git a/src/ocispec/json_api.py b/src/ocispec/json_api.py index 88c2d29a..768c6974 100644 --- a/src/ocispec/json_api.py +++ b/src/ocispec/json_api.py @@ -32,170 +32,132 @@ All JSON library-specific C code fragments are centralized in this module. To switch to a different JSON library, modify this module only -- the rest of the generator uses these names and helpers exclusively. + +Current backend: yyjson """ # --------------------------------------------------------------------------- # C type names # --------------------------------------------------------------------------- -VAL_TYPE = "yajl_val" -GEN_TYPE = "yajl_gen" -GEN_STATUS_TYPE = "yajl_gen_status" -GEN_STATUS_OK = "yajl_gen_status_ok" +VAL_TYPE = "yyjson_val *" +DOC_TYPE = "yyjson_doc *" +GEN_TYPE = "json_gen_ctx *" +GEN_TYPE_NAME = "json_gen_ctx" # without pointer, for function names +GEN_STATUS_TYPE = "json_gen_status" +GEN_STATUS_OK = "json_gen_status_ok" +RESIDUAL_TYPE = "char *" # --------------------------------------------------------------------------- # Type constants passed to get_val() # --------------------------------------------------------------------------- -TYPE_STRING = "yajl_t_string" -TYPE_NUMBER = "yajl_t_number" -TYPE_OBJECT = "yajl_t_object" -TYPE_ARRAY = "yajl_t_array" -TYPE_TRUE = "yajl_t_true" -TYPE_FALSE = "yajl_t_false" +TYPE_STRING = "YYJSON_TYPE_STR" +TYPE_NUMBER = "YYJSON_TYPE_RAW" +TYPE_OBJECT = "YYJSON_TYPE_OBJ" +TYPE_ARRAY = "YYJSON_TYPE_ARR" +TYPE_BOOL = "YYJSON_TYPE_BOOL" +TYPE_TRUE = TYPE_BOOL +TYPE_FALSE = TYPE_BOOL # --------------------------------------------------------------------------- # Value extraction -- each returns a C expression string # --------------------------------------------------------------------------- def get_string(val): - return f"YAJL_GET_STRING ({val})" + return f"yyjson_get_str ({val})" def get_number(val): - return f"YAJL_GET_NUMBER ({val})" + return f"yyjson_get_raw ({val})" def is_number(val): - return f"YAJL_IS_NUMBER ({val})" + return f"yyjson_is_raw ({val})" def is_true(val): - return f"YAJL_IS_TRUE ({val})" + return f"yyjson_get_bool ({val})" # --------------------------------------------------------------------------- # Array access -- each returns a C expression string # --------------------------------------------------------------------------- def array_check(val): - return f"YAJL_GET_ARRAY ({val}) != NULL" + return f"yyjson_is_arr ({val})" def array_len(val): - return f"YAJL_GET_ARRAY_NO_CHECK ({val})->len" + return f"yyjson_arr_size ({val})" def array_get(val, idx): - return f"YAJL_GET_ARRAY_NO_CHECK ({val})->values[{idx}]" - -def array_values(val): - return f"YAJL_GET_ARRAY_NO_CHECK ({val})->values" + return f"yyjson_arr_get ({val}, {idx})" # --------------------------------------------------------------------------- # Object access -- each returns a C expression string # --------------------------------------------------------------------------- def object_check(val): - return f"YAJL_GET_OBJECT ({val}) != NULL" + return f"yyjson_is_obj ({val})" def object_len(val): - return f"YAJL_GET_OBJECT_NO_CHECK ({val})->len" - -def object_keys(val): - return f"YAJL_GET_OBJECT_NO_CHECK ({val})->keys" - -def object_values(val): - return f"YAJL_GET_OBJECT_NO_CHECK ({val})->values" - -# --------------------------------------------------------------------------- -# Internal structure access (for residual handling) -# --------------------------------------------------------------------------- - -def object_type_field(var): - return f"{var}->type" - -def object_type_value(): - return TYPE_OBJECT - -def set_object_type(var): - return f"{var}->type = {TYPE_OBJECT}" - -def object_keys_field(var): - return f"{var}->u.object.keys" - -def object_values_field(var): - return f"{var}->u.object.values" - -def alloc_object_keys(var, count): - return f"{var}->u.object.keys = calloc ({count}, sizeof (const char *))" - -def alloc_object_values(var, count): - return f"{var}->u.object.values = calloc ({count}, sizeof ({VAL_TYPE}))" - -def object_key_direct(var, idx): - return f"{var}->u.object.keys[{idx}]" - -def object_value_direct(var, idx): - return f"{var}->u.object.values[{idx}]" - -def object_len_field(var): - return f"{var}->u.object.len" + return f"yyjson_obj_size ({val})" # --------------------------------------------------------------------------- # Generation -- each returns a C expression string # --------------------------------------------------------------------------- def gen_string(gen, str_expr, len_expr): - return f"yajl_gen_string (({GEN_TYPE}) {gen}, (const unsigned char *)({str_expr}), {len_expr})" + return f"json_gen_string ({gen}, (const char *) ({str_expr}), {len_expr})" def gen_bool(gen, val): - return f"yajl_gen_bool (({GEN_TYPE}){gen}, (int)({val}))" + return f"json_gen_bool ({gen}, (int) ({val}))" def gen_double(gen, val): - return f"yajl_gen_double (({GEN_TYPE}){gen}, {val})" + return f"json_gen_double ({gen}, {val})" def gen_map_open(gen): - return f"yajl_gen_map_open (({GEN_TYPE}) {gen})" + return f"json_gen_map_open ({gen})" def gen_map_close(gen): - return f"yajl_gen_map_close (({GEN_TYPE}) {gen})" + return f"json_gen_map_close ({gen})" def gen_array_open(gen): - return f"yajl_gen_array_open (({GEN_TYPE}) {gen})" + return f"json_gen_array_open ({gen})" def gen_array_close(gen): - return f"yajl_gen_array_close (({GEN_TYPE}) {gen})" + return f"json_gen_array_close ({gen})" # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- -GEN_BEAUTIFY = "yajl_gen_beautify" +GEN_BEAUTIFY = "json_gen_beautify" def gen_config(gen, option, val): - return f"yajl_gen_config ({gen}, {option}, {val})" + return f"json_gen_config ({gen}, {option}, {val})" # --------------------------------------------------------------------------- # Buffer access and cleanup # --------------------------------------------------------------------------- def gen_get_buf(gen, buf_var, len_var): - return f"yajl_gen_get_buf ({gen}, {buf_var}, {len_var})" - -def gen_clear(gen): - return f"yajl_gen_clear ({gen})" + return f"json_gen_get_buf ({gen}, {buf_var}, {len_var})" def gen_free(gen): - return f"yajl_gen_free ({gen})" + return f"json_gen_free ({gen})" # --------------------------------------------------------------------------- # Parsing and lifecycle # --------------------------------------------------------------------------- -def tree_parse(data, buf, size): - return f"yajl_tree_parse ({data}, {buf}, {size})" +READ_FLAGS = "YYJSON_READ_NUMBER_AS_RAW" +DOC_FREE_FUNC = "yyjson_doc_free" +TREE_FREE_FUNC = DOC_FREE_FUNC -TREE_FREE_FUNC = "yajl_tree_free" +def doc_read(data, len_expr): + return f"yyjson_read ({data}, {len_expr}, {READ_FLAGS})" -def tree_free(val): - return f"{TREE_FREE_FUNC} ({val})" +def doc_get_root(doc): + return f"yyjson_doc_get_root ({doc})" # --------------------------------------------------------------------------- # Residual serialization # --------------------------------------------------------------------------- -GEN_RESIDUAL_FUNC = "gen_yajl_object_residual" +GEN_RESIDUAL_FUNC = "gen_json_object_residual" def gen_residual(obj, gen, err): return f"{GEN_RESIDUAL_FUNC} ({obj}, {gen}, {err})" @@ -205,10 +167,7 @@ def gen_residual(obj, gen, err): # --------------------------------------------------------------------------- def get_prologue_defines(): - return [ - "#define YAJL_GET_ARRAY_NO_CHECK(v) (&(v)->u.array)\n", - "#define YAJL_GET_OBJECT_NO_CHECK(v) (&(v)->u.object)\n", - ] + return [] # --------------------------------------------------------------------------- # Cleanup helpers emitted once per generated .c file (epilog) @@ -218,17 +177,16 @@ def get_gen_cleanup_block(): """Return the static cleanup function for the JSON generator.""" return f"""\ static void -cleanup_{GEN_TYPE} ({GEN_TYPE} g) +cleanup_{GEN_TYPE_NAME} ({GEN_TYPE}g) {{ - if (!g) - return; - {gen_clear('g')}; - {gen_free('g')}; + if (! g) + return; + {gen_free('g')}; }} -define_cleaner_function ({GEN_TYPE}, cleanup_{GEN_TYPE}) +define_cleaner_function ({GEN_TYPE}, cleanup_{GEN_TYPE_NAME}) """ def get_val_cleaner_define(): - """Return the cleaner macro for the JSON value tree.""" - return f"define_cleaner_function ({VAL_TYPE}, {TREE_FREE_FUNC})" + """Return the cleaner macro for the JSON document.""" + return f"define_cleaner_function ({DOC_TYPE}, {DOC_FREE_FUNC})" diff --git a/src/ocispec/json_common.c b/src/ocispec/json_common.c index d483cdcf..17d598b3 100644 --- a/src/ocispec/json_common.c +++ b/src/ocispec/json_common.c @@ -5,130 +5,378 @@ #include #include "ocispec/json_common.h" -#define YAJL_GET_OBJECT_NO_CHECK(v) (&(v)->u.object) -#define YAJL_GET_STRING_NO_CHECK(v) ((v)->u.string) - #define MAX_NUM_STR_LEN 21 -static yajl_gen_status gen_yajl_val (yajl_val obj, yajl_gen g, parser_error *err); +/* --------------------------------------------------------------------------- + * Streaming JSON generator -- wraps yyjson_mut_doc + * ---------------------------------------------------------------------------*/ -static yajl_gen_status -gen_yajl_val_obj (yajl_val obj, yajl_gen g, parser_error *err) +static json_gen_status +add_value (json_gen_ctx *g, yyjson_mut_val *val) { - size_t i; - yajl_gen_status stat = yajl_gen_status_ok; + if (val == NULL) + return json_gen_in_error_state; - stat = yajl_gen_map_open (g); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); + if (g->depth < 0) + { + /* Top-level value (no container open yet). */ + g->root = val; + return json_gen_status_ok; + } - for (i = 0; i < obj->u.object.len; i++) + if (g->is_map[g->depth]) { - stat = yajl_gen_string (g, (const unsigned char *) obj->u.object.keys[i], strlen (obj->u.object.keys[i])); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); - stat = gen_yajl_val (obj->u.object.values[i], g, err); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); + if (g->keys[g->depth] != NULL) + { + /* We have a pending key — add key:value pair. */ + yyjson_mut_obj_add (g->stack[g->depth], g->keys[g->depth], val); + g->keys[g->depth] = NULL; + } + else + { + /* No pending key in a map — this shouldn't happen. */ + return json_gen_in_error_state; + } + } + else + { + /* We're in an array — append. */ + yyjson_mut_arr_append (g->stack[g->depth], val); } - stat = yajl_gen_map_close (g); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); - return yajl_gen_status_ok; + return json_gen_status_ok; } -static yajl_gen_status -gen_yajl_val_array (yajl_val arr, yajl_gen g, parser_error *err) +json_gen_status +json_gen_map_open (json_gen_ctx *g) { - size_t i; - yajl_gen_status stat = yajl_gen_status_ok; + yyjson_mut_val *obj; - stat = yajl_gen_array_open (g); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); + if (g->depth + 1 >= JSON_GEN_MAX_DEPTH) + return json_gen_in_error_state; + + obj = yyjson_mut_obj (g->doc); + if (obj == NULL) + return json_gen_in_error_state; + + g->depth++; + g->stack[g->depth] = obj; + g->is_map[g->depth] = true; + g->keys[g->depth] = NULL; + + return json_gen_status_ok; +} + +json_gen_status +json_gen_map_close (json_gen_ctx *g) +{ + yyjson_mut_val *obj; + + if (g->depth < 0) + return json_gen_in_error_state; + + obj = g->stack[g->depth]; + g->depth--; + + return add_value (g, obj); +} + +json_gen_status +json_gen_array_open (json_gen_ctx *g) +{ + yyjson_mut_val *arr; + + if (g->depth + 1 >= JSON_GEN_MAX_DEPTH) + return json_gen_in_error_state; + + arr = yyjson_mut_arr (g->doc); + if (arr == NULL) + return json_gen_in_error_state; + + g->depth++; + g->stack[g->depth] = arr; + g->is_map[g->depth] = false; + g->keys[g->depth] = NULL; + + return json_gen_status_ok; +} + +json_gen_status +json_gen_array_close (json_gen_ctx *g) +{ + yyjson_mut_val *arr; + + if (g->depth < 0) + return json_gen_in_error_state; + + arr = g->stack[g->depth]; + g->depth--; + + return add_value (g, arr); +} + +json_gen_status +json_gen_string (json_gen_ctx *g, const char *str, size_t len) +{ + yyjson_mut_val *val; - for (i = 0; i < arr->u.array.len; i++) + if (g->depth >= 0 && g->is_map[g->depth] && g->keys[g->depth] == NULL) { - stat = gen_yajl_val (arr->u.array.values[i], g, err); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); + /* In a map without a pending key — this string is the key. */ + g->keys[g->depth] = yyjson_mut_strncpy (g->doc, str, len); + if (g->keys[g->depth] == NULL) + return json_gen_in_error_state; + return json_gen_status_ok; } - stat = yajl_gen_array_close (g); - if (yajl_gen_status_ok != stat) - GEN_SET_ERROR_AND_RETURN (stat, err); - return yajl_gen_status_ok; + val = yyjson_mut_strncpy (g->doc, str, len); + return add_value (g, val); +} + +json_gen_status +json_gen_number (json_gen_ctx *g, const char *numstr, size_t len) +{ + yyjson_mut_val *val = yyjson_mut_rawncpy (g->doc, numstr, len); + return add_value (g, val); +} + +json_gen_status +json_gen_bool (json_gen_ctx *g, int val) +{ + yyjson_mut_val *v = yyjson_mut_bool (g->doc, val); + return add_value (g, v); +} + +json_gen_status +json_gen_double (json_gen_ctx *g, double val) +{ + yyjson_mut_val *v = yyjson_mut_real (g->doc, val); + return add_value (g, v); } -static yajl_gen_status -gen_yajl_val (yajl_val obj, yajl_gen g, parser_error *err) +json_gen_status +json_gen_null (json_gen_ctx *g) { - yajl_gen_status __stat = yajl_gen_status_ok; - char *__tstr; + yyjson_mut_val *v = yyjson_mut_null (g->doc); + return add_value (g, v); +} + +json_gen_status +json_gen_get_buf (json_gen_ctx *g, const char **buf, size_t *len) +{ + yyjson_write_flag flags = 0; - switch (obj->type) + /* Free previous buffer if any. */ + if (g->buf != NULL) { - case yajl_t_string: - __tstr = YAJL_GET_STRING (obj); - if (__tstr == NULL) - { - return __stat; - } - __stat = yajl_gen_string (g, (const unsigned char *) __tstr, strlen (__tstr)); - if (yajl_gen_status_ok != __stat) - GEN_SET_ERROR_AND_RETURN (__stat, err); - return yajl_gen_status_ok; - case yajl_t_number: - __tstr = YAJL_GET_NUMBER (obj); - if (__tstr == NULL) - { - return __stat; - } - __stat = yajl_gen_number (g, __tstr, strlen (__tstr)); - if (yajl_gen_status_ok != __stat) - GEN_SET_ERROR_AND_RETURN (__stat, err); - return yajl_gen_status_ok; - case yajl_t_object: - return gen_yajl_val_obj (obj, g, err); - case yajl_t_array: - return gen_yajl_val_array (obj, g, err); - case yajl_t_true: - return yajl_gen_bool (g, true); - case yajl_t_false: - return yajl_gen_bool (g, false); - case yajl_t_null: - return yajl_gen_null(g); - case yajl_t_any: - return __stat; + free (g->buf); + g->buf = NULL; } - return __stat; + + if (g->beautify) + flags |= YYJSON_WRITE_PRETTY; + + if (g->root == NULL) + return json_gen_in_error_state; + + yyjson_mut_doc_set_root (g->doc, g->root); + g->buf = yyjson_mut_write (g->doc, flags, &g->buf_len); + if (g->buf == NULL) + return json_gen_in_error_state; + + /* Strip trailing newline if present. */ + if (g->buf_len > 0 && g->buf[g->buf_len - 1] == '\n') + { + g->buf_len--; + g->buf[g->buf_len] = '\0'; + } + + *buf = g->buf; + *len = g->buf_len; + return json_gen_status_ok; } -yajl_gen_status -gen_yajl_object_residual (yajl_val obj, yajl_gen g, parser_error *err) +void +json_gen_config (json_gen_ctx *g, int option, int val) { - size_t i; - yajl_gen_status stat = yajl_gen_status_ok; + if (g == NULL) + return; + if (option == json_gen_beautify) + g->beautify = (val != 0); +} + +void +json_gen_free (json_gen_ctx *g) +{ + if (g == NULL) + return; + if (g->buf != NULL) + free (g->buf); + if (g->doc != NULL) + yyjson_mut_doc_free (g->doc); + free (g); +} + +/* --------------------------------------------------------------------------- + * Residual generation -- parse stored JSON string, inject into gen context + * ---------------------------------------------------------------------------*/ - for (i = 0; i < obj->u.object.len; i++) +static json_gen_status +gen_json_val (yyjson_val *val, json_gen_ctx *g, parser_error *err) +{ + json_gen_status stat = json_gen_status_ok; + yyjson_type type = yyjson_get_type (val); + + switch (type) { - if (obj->u.object.keys[i] == NULL) - { - continue; - } - stat = yajl_gen_string (g, (const unsigned char *) obj->u.object.keys[i], strlen (obj->u.object.keys[i])); - if (yajl_gen_status_ok != stat) + case YYJSON_TYPE_STR: + { + const char *str = yyjson_get_str (val); + if (str == NULL) + return stat; + stat = json_gen_string (g, str, strlen (str)); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + } + case YYJSON_TYPE_NUM: + { + char numstr[MAX_NUM_STR_LEN]; + int nret; + yyjson_subtype subtype = yyjson_get_subtype (val); + if (subtype == YYJSON_SUBTYPE_UINT) + nret = snprintf (numstr, sizeof (numstr), "%llu", (unsigned long long) yyjson_get_uint (val)); + else if (subtype == YYJSON_SUBTYPE_SINT) + nret = snprintf (numstr, sizeof (numstr), "%lld", (long long) yyjson_get_sint (val)); + else + nret = snprintf (numstr, sizeof (numstr), "%g", yyjson_get_real (val)); + if (nret < 0 || (size_t) nret >= sizeof (numstr)) + return json_gen_in_error_state; + stat = json_gen_number (g, numstr, strlen (numstr)); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + } + case YYJSON_TYPE_RAW: + { + const char *raw = yyjson_get_raw (val); + if (raw == NULL) + return stat; + stat = json_gen_number (g, raw, strlen (raw)); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + } + case YYJSON_TYPE_BOOL: + stat = json_gen_bool (g, yyjson_get_bool (val)); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); - stat = gen_yajl_val (obj->u.object.values[i], g, err); - if (yajl_gen_status_ok != stat) + return json_gen_status_ok; + case YYJSON_TYPE_NULL: + stat = json_gen_null (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + case YYJSON_TYPE_OBJ: + { + yyjson_obj_iter iter; + yyjson_val *key; + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + yyjson_obj_iter_init (val, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) + { + yyjson_val *v = yyjson_obj_iter_get_val (key); + const char *kstr = yyjson_get_str (key); + stat = json_gen_string (g, kstr, strlen (kstr)); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + stat = gen_json_val (v, g, err); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + } + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + } + case YYJSON_TYPE_ARR: + { + yyjson_arr_iter iter; + yyjson_val *v; + stat = json_gen_array_open (g); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + yyjson_arr_iter_init (val, &iter); + while ((v = yyjson_arr_iter_next (&iter)) != NULL) + { + stat = gen_json_val (v, g, err); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + } + stat = json_gen_array_close (g); + if (json_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN (stat, err); + return json_gen_status_ok; + } + default: + return stat; + } +} + +json_gen_status +gen_json_object_residual (const char *residual, json_gen_ctx *g, parser_error *err) +{ + json_gen_status stat = json_gen_status_ok; + yyjson_doc *doc; + yyjson_val *root; + yyjson_obj_iter iter; + yyjson_val *key; + + if (residual == NULL) + return json_gen_status_ok; + + doc = yyjson_read (residual, strlen (residual), YYJSON_READ_NUMBER_AS_RAW); + if (doc == NULL) + return json_gen_in_error_state; + + root = yyjson_doc_get_root (doc); + if (root == NULL || ! yyjson_is_obj (root)) + { + yyjson_doc_free (doc); + return json_gen_in_error_state; + } + + yyjson_obj_iter_init (root, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) + { + yyjson_val *v = yyjson_obj_iter_get_val (key); + const char *kstr = yyjson_get_str (key); + + stat = json_gen_string (g, kstr, strlen (kstr)); + if (json_gen_status_ok != stat) + { + yyjson_doc_free (doc); + GEN_SET_ERROR_AND_RETURN (stat, err); + } + stat = gen_json_val (v, g, err); + if (json_gen_status_ok != stat) + { + yyjson_doc_free (doc); + GEN_SET_ERROR_AND_RETURN (stat, err); + } } - return yajl_gen_status_ok; + yyjson_doc_free (doc); + return json_gen_status_ok; } -yajl_gen_status +/* --------------------------------------------------------------------------- + * map_uint / map_int -- write a number to the generator + * ---------------------------------------------------------------------------*/ + +json_gen_status map_uint (void *ctx, long long unsigned int num) { char numstr[MAX_NUM_STR_LEN]; @@ -136,11 +384,11 @@ map_uint (void *ctx, long long unsigned int num) ret = snprintf (numstr, sizeof (numstr), "%llu", num); if (ret < 0 || (size_t) ret >= sizeof (numstr)) - return yajl_gen_in_error_state; - return yajl_gen_number ((yajl_gen) ctx, (const char *) numstr, strlen (numstr)); + return json_gen_in_error_state; + return json_gen_number ((json_gen_ctx *) ctx, (const char *) numstr, strlen (numstr)); } -yajl_gen_status +json_gen_status map_int (void *ctx, long long int num) { char numstr[MAX_NUM_STR_LEN]; @@ -148,29 +396,57 @@ map_int (void *ctx, long long int num) ret = snprintf (numstr, sizeof (numstr), "%lld", num); if (ret < 0 || (size_t) ret >= sizeof (numstr)) - return yajl_gen_in_error_state; - return yajl_gen_number ((yajl_gen) ctx, (const char *) numstr, strlen (numstr)); + return json_gen_in_error_state; + return json_gen_number ((json_gen_ctx *) ctx, (const char *) numstr, strlen (numstr)); } +/* --------------------------------------------------------------------------- + * json_gen_init -- allocate and configure generator context + * ---------------------------------------------------------------------------*/ + bool -json_gen_init (yajl_gen *g, const struct parser_context *ctx) +json_gen_init (json_gen_ctx **g, const struct parser_context *ctx) { - *g = yajl_gen_alloc (NULL); - if (NULL == *g) + json_gen_ctx *gen = calloc (1, sizeof (json_gen_ctx)); + if (gen == NULL) return false; - yajl_gen_config (*g, yajl_gen_beautify, (int) (! (ctx->options & OPT_GEN_SIMPLIFY))); - yajl_gen_config (*g, yajl_gen_validate_utf8, (int) (! (ctx->options & OPT_GEN_NO_VALIDATE_UTF8))); + gen->doc = yyjson_mut_doc_new (NULL); + if (gen->doc == NULL) + { + free (gen); + return false; + } + + gen->depth = -1; + gen->root = NULL; + gen->buf = NULL; + gen->buf_len = 0; + gen->beautify = (ctx == NULL) || ! (ctx->options & OPT_GEN_SIMPLIFY); + + *g = gen; return true; } -yajl_val -get_val (yajl_val tree, const char *name, yajl_type type) +/* --------------------------------------------------------------------------- + * get_val -- look up a key in an object, optionally filtering by type + * ---------------------------------------------------------------------------*/ + +yyjson_val * +get_val (yyjson_val *tree, const char *name, yyjson_type type) { - const char *path[] = { name, NULL }; - return yajl_tree_get (tree, path, type); + yyjson_val *val = yyjson_obj_get (tree, name); + if (val == NULL) + return NULL; + if (type != 0 && yyjson_get_type (val) != type) + return NULL; + return val; } +/* --------------------------------------------------------------------------- + * safe_strdup / safe_malloc -- abort on failure + * ---------------------------------------------------------------------------*/ + char * safe_strdup (const char *src) { @@ -196,6 +472,10 @@ safe_malloc (size_t size) return ret; } +/* --------------------------------------------------------------------------- + * common_safe_* -- numeric string conversions + * ---------------------------------------------------------------------------*/ + int common_safe_double (const char *numstr, double *converted) { @@ -454,18 +734,22 @@ common_safe_int (const char *numstr, int *converted) return 0; } -yajl_gen_status +/* --------------------------------------------------------------------------- + * gen_json_map_* / make_json_map_* / free_json_map_* / append_json_map_* + * ---------------------------------------------------------------------------*/ + +json_gen_status gen_json_map_int_int (void *ctx, const json_map_int_int *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + json_gen_config (g, json_gen_beautify, 0); + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { @@ -476,22 +760,22 @@ gen_json_map_int_int (void *ctx, const json_map_int_int *map, const struct parse { if (! *err) *err = strdup ("Error to print string"); - return yajl_gen_in_error_state; + return json_gen_in_error_state; } - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) numstr, strlen (numstr)); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, numstr, strlen (numstr)); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); stat = map_int (g, map->values[i]); - if (yajl_gen_status_ok != stat) + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -510,18 +794,20 @@ free_json_map_int_int (json_map_int_int *map) define_cleaner_function (json_map_int_int *, free_json_map_int_int) json_map_int_int * -make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_error *err) +make_json_map_int_int (yyjson_val *src, const struct parser_context *ctx, parser_error *err) { __auto_cleanup (free_json_map_int_int) json_map_int_int *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; (void) ctx; /* Silence compiler warning. */ - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) return NULL; @@ -539,10 +825,12 @@ make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_er return NULL; } - for (i = 0; i < len; i++) + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = 0; ret->values[i] = 0; @@ -565,7 +853,7 @@ make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_er if (srcval != NULL) { int invalid; - if (! YAJL_IS_NUMBER (srcval)) + if (! yyjson_is_raw (srcval)) { if (*err == NULL && asprintf (err, "Invalid value with type 'int' for key '%s'", srckey) < 0) { @@ -573,7 +861,7 @@ make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_er } return NULL; } - invalid = common_safe_int (YAJL_GET_NUMBER (srcval), &(ret->values[i])); + invalid = common_safe_int (yyjson_get_raw (srcval), &(ret->values[i])); if (invalid) { if (*err == NULL @@ -584,6 +872,7 @@ make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_er return NULL; } } + i++; } return move_ptr (ret); } @@ -629,18 +918,18 @@ append_json_map_int_int (json_map_int_int *map, int key, int val) return 0; } -yajl_gen_status +json_gen_status gen_json_map_int_bool (void *ctx, const json_map_int_bool *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + json_gen_config (g, json_gen_beautify, 0); + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { @@ -651,22 +940,22 @@ gen_json_map_int_bool (void *ctx, const json_map_int_bool *map, const struct par { if (! *err) *err = strdup ("Error to print string"); - return yajl_gen_in_error_state; + return json_gen_in_error_state; } - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) numstr, strlen (numstr)); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, numstr, strlen (numstr)); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); - stat = yajl_gen_bool ((yajl_gen) g, (int) (map->values[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_bool (g, (int) (map->values[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -691,18 +980,20 @@ free_json_map_int_bool (json_map_int_bool *map) define_cleaner_function (json_map_int_bool *, free_json_map_int_bool) json_map_int_bool * -make_json_map_int_bool (yajl_val src, const struct parser_context *ctx, parser_error *err) +make_json_map_int_bool (yyjson_val *src, const struct parser_context *ctx, parser_error *err) { __auto_cleanup (free_json_map_int_bool) json_map_int_bool *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; (void) ctx; /* Silence compiler warning. */ - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) return NULL; @@ -717,10 +1008,13 @@ make_json_map_int_bool (yajl_val src, const struct parser_context *ctx, parser_e { return NULL; } - for (i = 0; i < len; i++) + + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = 0; ret->values[i] = false; @@ -742,9 +1036,9 @@ make_json_map_int_bool (yajl_val src, const struct parser_context *ctx, parser_e if (srcval != NULL) { - if (YAJL_IS_TRUE (srcval)) + if (yyjson_is_true (srcval)) ret->values[i] = true; - else if (YAJL_IS_FALSE (srcval)) + else if (yyjson_is_false (srcval)) ret->values[i] = false; else { @@ -755,6 +1049,7 @@ make_json_map_int_bool (yajl_val src, const struct parser_context *ctx, parser_e return NULL; } } + i++; } return move_ptr (ret); } @@ -800,19 +1095,19 @@ append_json_map_int_bool (json_map_int_bool *map, int key, bool val) return 0; } -yajl_gen_status +json_gen_status gen_json_map_int_string (void *ctx, const json_map_int_string *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); + json_gen_config (g, json_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { @@ -823,22 +1118,22 @@ gen_json_map_int_string (void *ctx, const json_map_int_string *map, const struct { if (! *err) *err = strdup ("Error to print string"); - return yajl_gen_in_error_state; + return json_gen_in_error_state; } - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) numstr, strlen (numstr)); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, numstr, strlen (numstr)); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->values[i]), strlen (map->values[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->values[i], strlen (map->values[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -864,18 +1159,20 @@ free_json_map_int_string (json_map_int_string *map) define_cleaner_function (json_map_int_string *, free_json_map_int_string) json_map_int_string * -make_json_map_int_string (yajl_val src, const struct parser_context *ctx, parser_error *err) +make_json_map_int_string (yyjson_val *src, const struct parser_context *ctx, parser_error *err) { __auto_cleanup (free_json_map_int_string) json_map_int_string *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; (void) ctx; /* Silence compiler warning. */ - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) @@ -894,10 +1191,12 @@ make_json_map_int_string (yajl_val src, const struct parser_context *ctx, parser return NULL; } - for (i = 0; i < len; i++) + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = 0; ret->values[i] = NULL; @@ -920,7 +1219,8 @@ make_json_map_int_string (yajl_val src, const struct parser_context *ctx, parser if (srcval != NULL) { - if (! YAJL_IS_STRING (srcval)) + const char *str; + if (! yyjson_is_str (srcval)) { if (*err == NULL && asprintf (err, "Invalid value with type 'string' for key '%s'", srckey) < 0) { @@ -928,9 +1228,10 @@ make_json_map_int_string (yajl_val src, const struct parser_context *ctx, parser } return NULL; } - char *str = YAJL_GET_STRING_NO_CHECK (srcval); + str = yyjson_get_str (srcval); ret->values[i] = strdup (str ? str : ""); } + i++; } return move_ptr (ret); } @@ -971,35 +1272,35 @@ append_json_map_int_string (json_map_int_string *map, int key, const char *val) return 0; } -yajl_gen_status +json_gen_status gen_json_map_string_int (void *ctx, const json_map_string_int *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + json_gen_config (g, json_gen_beautify, 0); + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->keys[i]), strlen (map->keys[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->keys[i], strlen (map->keys[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); stat = map_int (g, map->values[i]); - if (yajl_gen_status_ok != stat) + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -1024,18 +1325,20 @@ free_json_map_string_int (json_map_string_int *map) define_cleaner_function (json_map_string_int *, free_json_map_string_int) json_map_string_int * -make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser_error *err) +make_json_map_string_int (yyjson_val *src, const struct parser_context *ctx, parser_error *err) { __auto_cleanup (free_json_map_string_int) json_map_string_int *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; (void) ctx; /* Silence compiler warning. */ - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) { @@ -1055,10 +1358,13 @@ make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser *(err) = strdup ("error allocating memory"); return NULL; } - for (i = 0; i < len; i++) + + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = NULL; ret->values[i] = 0; @@ -1074,7 +1380,7 @@ make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser if (srcval != NULL) { int invalid; - if (! YAJL_IS_NUMBER (srcval)) + if (! yyjson_is_raw (srcval)) { if (*err == NULL && asprintf (err, "Invalid value with type 'int' for key '%s'", srckey) < 0) { @@ -1082,7 +1388,7 @@ make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser } return NULL; } - invalid = common_safe_int (YAJL_GET_NUMBER (srcval), &(ret->values[i])); + invalid = common_safe_int (yyjson_get_raw (srcval), &(ret->values[i])); if (invalid) { if (*err == NULL @@ -1093,6 +1399,7 @@ make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser return NULL; } } + i++; } return move_ptr (ret); } @@ -1131,37 +1438,37 @@ append_json_map_string_int (json_map_string_int *map, const char *key, int val) return 0; } -yajl_gen_status +json_gen_status gen_json_map_string_int64 (void *ctx, const json_map_string_int64 *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + json_gen_config (g, json_gen_beautify, 0); + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->keys[i]), strlen (map->keys[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->keys[i], strlen (map->keys[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); stat = map_int (g, map->values[i]); - if (yajl_gen_status_ok != stat) + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -1186,31 +1493,38 @@ free_json_map_string_int64 (json_map_string_int64 *map) define_cleaner_function (json_map_string_int64 *, free_json_map_string_int64) json_map_string_int64 * -make_json_map_string_int64 (yajl_val src, const struct parser_context *ctx, - parser_error *err) +make_json_map_string_int64 (yyjson_val *src, const struct parser_context *ctx, + parser_error *err) { __auto_cleanup (free_json_map_string_int64) json_map_string_int64 *ret = NULL; (void) ctx; /* Silence compiler warning. */ - if (src != NULL && YAJL_GET_OBJECT (src) != NULL) + if (src != NULL && yyjson_is_obj (src)) { size_t i; - size_t len = YAJL_GET_OBJECT (src)->len; + size_t len = yyjson_obj_size (src); + yyjson_obj_iter iter; + yyjson_val *key; + ret = safe_malloc (sizeof (*ret)); ret->len = len; ret->keys = safe_malloc ((len + 1) * sizeof (char *)); ret->values = safe_malloc ((len + 1) * sizeof (int64_t)); - for (i = 0; i < len; i++) + + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); + ret->keys[i] = safe_strdup (srckey ? srckey : ""); if (srcval != NULL) { int64_t invalid; - if (! YAJL_IS_NUMBER (srcval)) + if (! yyjson_is_raw (srcval)) { if (*err == NULL && asprintf (err, "Invalid value with type 'int' for key '%s'", srckey) < 0) { @@ -1218,7 +1532,7 @@ make_json_map_string_int64 (yajl_val src, const struct parser_context *ctx, } return NULL; } - invalid = common_safe_int64 (YAJL_GET_NUMBER (srcval), &(ret->values[i])); + invalid = common_safe_int64 (yyjson_get_raw (srcval), &(ret->values[i])); if (invalid) { if (*err == NULL @@ -1230,6 +1544,7 @@ make_json_map_string_int64 (yajl_val src, const struct parser_context *ctx, return NULL; } } + i++; } } return move_ptr (ret); @@ -1267,36 +1582,36 @@ append_json_map_string_int64 (json_map_string_int64 *map, const char *key, int64 return 0; } -yajl_gen_status +json_gen_status gen_json_map_string_bool (void *ctx, const json_map_string_bool *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + json_gen_config (g, json_gen_beautify, 0); + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->keys[i]), strlen (map->keys[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->keys[i], strlen (map->keys[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); - stat = yajl_gen_bool ((yajl_gen) g, (int) (map->values[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_bool (g, (int) (map->values[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -1322,18 +1637,20 @@ free_json_map_string_bool (json_map_string_bool *map) define_cleaner_function (json_map_string_bool *, free_json_map_string_bool) json_map_string_bool * -make_json_map_string_bool (yajl_val src, const struct parser_context *ctx, parser_error *err) +make_json_map_string_bool (yyjson_val *src, const struct parser_context *ctx, parser_error *err) { __auto_cleanup (free_json_map_string_bool) json_map_string_bool *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; (void) ctx; /* Silence compiler warning. */ - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) @@ -1350,13 +1667,16 @@ make_json_map_string_bool (yajl_val src, const struct parser_context *ctx, parse { return NULL; } - for (i = 0; i < len; i++) + + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = NULL; - ret->values[i] = NULL; + ret->values[i] = false; ret->len = i + 1; ret->keys[i] = strdup (srckey ? srckey : ""); @@ -1367,9 +1687,9 @@ make_json_map_string_bool (yajl_val src, const struct parser_context *ctx, parse } if (srcval != NULL) { - if (YAJL_IS_TRUE (srcval)) + if (yyjson_is_true (srcval)) ret->values[i] = true; - else if (YAJL_IS_FALSE (srcval)) + else if (yyjson_is_false (srcval)) ret->values[i] = false; else { @@ -1380,6 +1700,7 @@ make_json_map_string_bool (yajl_val src, const struct parser_context *ctx, parse return NULL; } } + i++; } return move_ptr (ret); } @@ -1433,39 +1754,39 @@ append_json_map_string_bool (json_map_string_bool *map, const char *key, bool va return 0; } -yajl_gen_status +json_gen_status gen_json_map_string_string (void *ctx, const json_map_string_string *map, const struct parser_context *ptx, parser_error *err) { - yajl_gen_status stat = yajl_gen_status_ok; - yajl_gen g = (yajl_gen) ctx; + json_gen_status stat = json_gen_status_ok; + json_gen_ctx *g = (json_gen_ctx *) ctx; size_t len = 0, i = 0; if (map != NULL) len = map->len; if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 0); + json_gen_config (g, json_gen_beautify, 0); - stat = yajl_gen_map_open ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_open (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); for (i = 0; i < len; i++) { - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->keys[i]), strlen (map->keys[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->keys[i], strlen (map->keys[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) (map->values[i]), strlen (map->values[i])); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, map->values[i], strlen (map->values[i])); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); } - stat = yajl_gen_map_close ((yajl_gen) g); - if (yajl_gen_status_ok != stat) + stat = json_gen_map_close (g); + if (json_gen_status_ok != stat) GEN_SET_ERROR_AND_RETURN (stat, err); if (! len && ! (ptx->options & OPT_GEN_SIMPLIFY)) - yajl_gen_config (g, yajl_gen_beautify, 1); - return yajl_gen_status_ok; + json_gen_config (g, json_gen_beautify, 1); + return json_gen_status_ok; } void @@ -1492,18 +1813,20 @@ free_json_map_string_string (json_map_string_string *map) define_cleaner_function (json_map_string_string *, free_json_map_string_string) json_map_string_string * -make_json_map_string_string (yajl_val src, const struct parser_context *ctx, - parser_error *err) +make_json_map_string_string (yyjson_val *src, const struct parser_context *ctx, + parser_error *err) { __auto_cleanup (free_json_map_string_string) json_map_string_string *ret = NULL; size_t i; size_t len; + yyjson_obj_iter iter; + yyjson_val *key; (void) ctx; /* Silence compiler warning. */ - if (src == NULL || YAJL_GET_OBJECT (src) == NULL) + if (src == NULL || ! yyjson_is_obj (src)) return NULL; - len = YAJL_GET_OBJECT_NO_CHECK (src)->len; + len = yyjson_obj_size (src); ret = calloc (1, sizeof (*ret)); if (ret == NULL) @@ -1527,10 +1850,13 @@ make_json_map_string_string (yajl_val src, const struct parser_context *ctx, *(err) = strdup ("error allocating memory"); return NULL; } - for (i = 0; i < len; i++) + + i = 0; + yyjson_obj_iter_init (src, &iter); + while ((key = yyjson_obj_iter_next (&iter)) != NULL) { - const char *srckey = YAJL_GET_OBJECT_NO_CHECK (src)->keys[i]; - yajl_val srcval = YAJL_GET_OBJECT_NO_CHECK (src)->values[i]; + const char *srckey = yyjson_get_str (key); + yyjson_val *srcval = yyjson_obj_iter_get_val (key); ret->keys[i] = NULL; ret->values[i] = NULL; @@ -1543,8 +1869,8 @@ make_json_map_string_string (yajl_val src, const struct parser_context *ctx, } if (srcval != NULL) { - char *str; - if (! YAJL_IS_STRING (srcval)) + const char *str; + if (! yyjson_is_str (srcval)) { if (*err == NULL && asprintf (err, "Invalid value with type 'string' for key '%s'", srckey) < 0) { @@ -1553,7 +1879,7 @@ make_json_map_string_string (yajl_val src, const struct parser_context *ctx, return NULL; } - str = YAJL_GET_STRING_NO_CHECK (srcval); + str = yyjson_get_str (srcval); ret->values[i] = strdup (str ? str : ""); if (ret->values[i] == NULL) @@ -1561,6 +1887,7 @@ make_json_map_string_string (yajl_val src, const struct parser_context *ctx, return NULL; } } + i++; } return move_ptr (ret); } @@ -1592,11 +1919,11 @@ clone_map_string_string (json_map_string_string *src) { ret->keys[i] = strdup (src->keys[i]); if (ret->keys[i] == NULL) - return NULL; + return NULL; ret->values[i] = strdup (src->values[i]); if (ret->values[i] == NULL) - return NULL; + return NULL; } return move_ptr (ret); } @@ -1660,26 +1987,29 @@ append_json_map_string_string (json_map_string_string *map, const char *key, con return 0; } +/* --------------------------------------------------------------------------- + * json_marshal_string -- marshal a C string to a JSON string value + * ---------------------------------------------------------------------------*/ + static void -cleanup_yajl_gen (yajl_gen g) +cleanup_json_gen_ctx (json_gen_ctx *g) { if (! g) return; - yajl_gen_clear (g); - yajl_gen_free (g); + json_gen_free (g); } -define_cleaner_function (yajl_gen, cleanup_yajl_gen) +define_cleaner_function (json_gen_ctx *, cleanup_json_gen_ctx) char * json_marshal_string (const char *str, size_t length, const struct parser_context *ctx, parser_error *err) { - __auto_cleanup (cleanup_yajl_gen) yajl_gen g = NULL; + __auto_cleanup (cleanup_json_gen_ctx) json_gen_ctx *g = NULL; struct parser_context tmp_ctx = { 0 }; - const unsigned char *gen_buf = NULL; + const char *gen_buf = NULL; char *json_buf = NULL; size_t gen_len = 0; - yajl_gen_status stat; + json_gen_status stat; if (str == NULL || err == NULL) return NULL; @@ -1693,14 +2023,14 @@ json_marshal_string (const char *str, size_t length, const struct parser_context *err = strdup ("Json_gen init failed"); return json_buf; } - stat = yajl_gen_string ((yajl_gen) g, (const unsigned char *) str, length); - if (yajl_gen_status_ok != stat) + stat = json_gen_string (g, str, length); + if (json_gen_status_ok != stat) { if (asprintf (err, "error generating json, errcode: %d", (int) stat) < 0) *err = strdup ("error allocating memory"); return json_buf; } - yajl_gen_get_buf (g, &gen_buf, &gen_len); + json_gen_get_buf (g, &gen_buf, &gen_len); if (gen_buf == NULL) { *err = strdup ("Error to get generated json"); diff --git a/src/ocispec/json_common.h b/src/ocispec/json_common.h index 41c73f59..73e41922 100644 --- a/src/ocispec/json_common.h +++ b/src/ocispec/json_common.h @@ -6,8 +6,7 @@ #include #include #include -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -16,12 +15,12 @@ extern "C" { #undef linux #ifdef __MUSL__ -#undef stdin -#undef stdout -#undef stderr -#define stdin stdin -#define stdout stdout -#define stderr stderr +# undef stdin +# undef stdout +# undef stderr +# define stdin stdin +# define stdout stdout +# define stderr stderr #endif // options to report error if there is unknown key found in json @@ -63,7 +62,7 @@ ptr_free_function (void *p) { \ if (*(err) == NULL) \ { \ - if (asprintf (err, "%s: %s: %d: error generating json, errcode: %u", __FILE__, __func__, __LINE__, stat) < 0) \ + if (asprintf (err, "%s: %s: %d: error generating json, errcode: %d", __FILE__, __func__, __LINE__, stat) < 0) \ { \ *(err) = strdup ("error allocating memory"); \ } \ @@ -79,15 +78,51 @@ struct parser_context FILE *errfile; }; -yajl_gen_status gen_yajl_object_residual (yajl_val obj, yajl_gen g, parser_error *err); +/* Streaming JSON generator context -- wraps yyjson_mut_doc. */ +#define JSON_GEN_MAX_DEPTH 64 -yajl_gen_status map_uint (void *ctx, long long unsigned int num); +typedef int json_gen_status; +#define json_gen_status_ok 0 +#define json_gen_in_error_state (-1) -yajl_gen_status map_int (void *ctx, long long int num); +/* Beautify config constant (used with json_gen_config). */ +#define json_gen_beautify 0 -bool json_gen_init (yajl_gen *g, const struct parser_context *ctx); - -yajl_val get_val (yajl_val tree, const char *name, yajl_type type); +typedef struct +{ + yyjson_mut_doc *doc; + yyjson_mut_val *stack[JSON_GEN_MAX_DEPTH]; + yyjson_mut_val *keys[JSON_GEN_MAX_DEPTH]; + bool is_map[JSON_GEN_MAX_DEPTH]; + int depth; + yyjson_mut_val *root; + char *buf; + size_t buf_len; + bool beautify; +} json_gen_ctx; + +json_gen_status json_gen_map_open (json_gen_ctx *g); +json_gen_status json_gen_map_close (json_gen_ctx *g); +json_gen_status json_gen_array_open (json_gen_ctx *g); +json_gen_status json_gen_array_close (json_gen_ctx *g); +json_gen_status json_gen_string (json_gen_ctx *g, const char *str, size_t len); +json_gen_status json_gen_number (json_gen_ctx *g, const char *numstr, size_t len); +json_gen_status json_gen_bool (json_gen_ctx *g, int val); +json_gen_status json_gen_double (json_gen_ctx *g, double val); +json_gen_status json_gen_null (json_gen_ctx *g); +json_gen_status json_gen_get_buf (json_gen_ctx *g, const char **buf, size_t *len); +void json_gen_config (json_gen_ctx *g, int option, int val); +void json_gen_free (json_gen_ctx *g); + +json_gen_status gen_json_object_residual (const char *residual, json_gen_ctx *g, parser_error *err); + +json_gen_status map_uint (void *ctx, long long unsigned int num); + +json_gen_status map_int (void *ctx, long long int num); + +bool json_gen_init (json_gen_ctx **g, const struct parser_context *ctx); + +yyjson_val *get_val (yyjson_val *tree, const char *name, yyjson_type type); char *safe_strdup (const char *src); @@ -124,9 +159,9 @@ typedef struct void free_json_map_int_int (json_map_int_int *map); -json_map_int_int *make_json_map_int_int (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_int_int *make_json_map_int_int (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_int_int (void *ctx, const json_map_int_int *map, const struct parser_context *ptx, +json_gen_status gen_json_map_int_int (void *ctx, const json_map_int_int *map, const struct parser_context *ptx, parser_error *err); int append_json_map_int_int (json_map_int_int *map, int key, int val); @@ -140,9 +175,9 @@ typedef struct void free_json_map_int_bool (json_map_int_bool *map); -json_map_int_bool *make_json_map_int_bool (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_int_bool *make_json_map_int_bool (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_int_bool (void *ctx, const json_map_int_bool *map, const struct parser_context *ptx, +json_gen_status gen_json_map_int_bool (void *ctx, const json_map_int_bool *map, const struct parser_context *ptx, parser_error *err); int append_json_map_int_bool (json_map_int_bool *map, int key, bool val); @@ -156,9 +191,9 @@ typedef struct void free_json_map_int_string (json_map_int_string *map); -json_map_int_string *make_json_map_int_string (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_int_string *make_json_map_int_string (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_int_string (void *ctx, const json_map_int_string *map, const struct parser_context *ptx, +json_gen_status gen_json_map_int_string (void *ctx, const json_map_int_string *map, const struct parser_context *ptx, parser_error *err); int append_json_map_int_string (json_map_int_string *map, int key, const char *val); @@ -172,9 +207,9 @@ typedef struct void free_json_map_string_int (json_map_string_int *map); -json_map_string_int *make_json_map_string_int (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_string_int *make_json_map_string_int (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_string_int (void *ctx, const json_map_string_int *map, const struct parser_context *ptx, +json_gen_status gen_json_map_string_int (void *ctx, const json_map_string_int *map, const struct parser_context *ptx, parser_error *err); int append_json_map_string_int (json_map_string_int *map, const char *key, int val); @@ -188,7 +223,7 @@ typedef struct void free_json_map_string_bool (json_map_string_bool *map); -json_map_string_bool *make_json_map_string_bool (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_string_bool *make_json_map_string_bool (yyjson_val *src, const struct parser_context *ctx, parser_error *err); typedef struct { @@ -199,14 +234,14 @@ typedef struct void free_json_map_string_int64 (json_map_string_int64 *map); -json_map_string_int64 *make_json_map_string_int64 (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_string_int64 *make_json_map_string_int64 (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_string_int64 (void *ctx, const json_map_string_int64 *map, +json_gen_status gen_json_map_string_int64 (void *ctx, const json_map_string_int64 *map, const struct parser_context *ptx, parser_error *err); int append_json_map_string_int64 (json_map_string_int64 *map, const char *key, int64_t val); -yajl_gen_status gen_json_map_string_bool (void *ctx, const json_map_string_bool *map, const struct parser_context *ptx, +json_gen_status gen_json_map_string_bool (void *ctx, const json_map_string_bool *map, const struct parser_context *ptx, parser_error *err); int append_json_map_string_bool (json_map_string_bool *map, const char *key, bool val); @@ -222,9 +257,9 @@ void free_json_map_string_string (json_map_string_string *map); json_map_string_string *clone_map_string_string (json_map_string_string *src); -json_map_string_string *make_json_map_string_string (yajl_val src, const struct parser_context *ctx, parser_error *err); +json_map_string_string *make_json_map_string_string (yyjson_val *src, const struct parser_context *ctx, parser_error *err); -yajl_gen_status gen_json_map_string_string (void *ctx, const json_map_string_string *map, +json_gen_status gen_json_map_string_string (void *ctx, const json_map_string_string *map, const struct parser_context *ptx, parser_error *err); int append_json_map_string_string (json_map_string_string *map, const char *key, const char *val); diff --git a/src/ocispec/read-file.c b/src/ocispec/read-file.c index 9aba1f46..5f3c27c3 100644 --- a/src/ocispec/read-file.c +++ b/src/ocispec/read-file.c @@ -41,7 +41,7 @@ fread_file (FILE *stream, size_t *length) if (pos >= 0 && pos < st.st_size) { off_t alloc_off = st.st_size - pos; - if (SIZE_MAX - 1 < (uintmax_t)(alloc_off)) + if (SIZE_MAX - 1 < (uintmax_t) (alloc_off)) { errno = ENOMEM; return NULL; @@ -52,7 +52,7 @@ fread_file (FILE *stream, size_t *length) } } - if (!(buf = malloc (alloc))) + if (! (buf = malloc (alloc))) return NULL; { @@ -97,7 +97,7 @@ fread_file (FILE *stream, size_t *length) else alloc = SIZE_MAX; - if (!(temp_buf = realloc (buf, alloc))) + if (! (temp_buf = realloc (buf, alloc))) { save_errno = errno; break; @@ -120,7 +120,7 @@ read_file (const char *path, size_t *length) char *buf; int save_errno; - if (!f) + if (! f) return NULL; buf = fread_file (f, length); diff --git a/src/ocispec/sources.py b/src/ocispec/sources.py index c1f20c3a..cc883fb6 100755 --- a/src/ocispec/sources.py +++ b/src/ocispec/sources.py @@ -40,9 +40,9 @@ def emit(c_file, code, indent=0): Args: c_file: List to append code lines to code: Multi-line string (will be dedented) - indent: Number of 4-space indentation levels + indent: Number of 2-space indentation levels """ - prefix = ' ' * indent + prefix = ' ' * indent for line in dedent(code).strip().split('\n'): if line: c_file.append(prefix + line + '\n') @@ -57,9 +57,9 @@ def free_and_null(c_file, ptr, field, indent=0): c_file: List to append code lines to ptr: Pointer variable name field: Field name (can include array indexing like '[i]') - indent: Number of 4-space indentation levels + indent: Number of 2-space indentation levels """ - prefix = ' ' * indent + prefix = ' ' * indent c_file.append(f"{prefix}free ({ptr}->{field});\n") c_file.append(f"{prefix}{ptr}->{field} = NULL;\n") @@ -70,9 +70,9 @@ def null_check_return(c_file, var, indent=0): Args: c_file: List to append code lines to var: Variable to check (can be expression like 'ret->field' or 'ret->field[i]') - indent: Number of 4-space indentation levels + indent: Number of 2-space indentation levels """ - prefix = ' ' * indent + prefix = ' ' * indent c_file.append(f"{prefix}if ({var} == NULL)\n") c_file.append(f"{prefix} return NULL;\n") @@ -85,9 +85,9 @@ def calloc_with_check(c_file, dest, count, sizeof_expr, indent=0): dest: Destination variable count: Count expression for calloc sizeof_expr: sizeof expression (the content inside sizeof()) - indent: Number of 4-space indentation levels + indent: Number of 2-space indentation levels """ - prefix = ' ' * indent + prefix = ' ' * indent c_file.append(f"{prefix}{dest} = calloc ({count}, sizeof ({sizeof_expr}));\n") c_file.append(f"{prefix}if ({dest} == NULL)\n") c_file.append(f"{prefix} return NULL;\n") @@ -98,11 +98,11 @@ def check_gen_status(c_file, indent=0): Args: c_file: List to append code lines to - indent: Number of 4-space indentation levels + indent: Number of 2-space indentation levels """ - prefix = ' ' * indent + prefix = ' ' * indent c_file.append(f"{prefix}if (stat != {json_api.GEN_STATUS_OK})\n") - c_file.append(f"{prefix} GEN_SET_ERROR_AND_RETURN (stat, err);\n") + c_file.append(f"{prefix} GEN_SET_ERROR_AND_RETURN (stat, err);\n") def do_read_value(c_file, src_expr, dest_expr, typ, origname, obj_typename, indent=1): @@ -123,8 +123,7 @@ def do_read_value(c_file, src_expr, dest_expr, typ, origname, obj_typename, inde ''', indent=indent) read_val_generator(c_file, indent + 1, src_expr, dest_expr, typ, origname, obj_typename) emit(c_file, f''' - }} - while (0); + }} while (0); ''', indent=indent) @@ -296,7 +295,6 @@ def emit_array_parse_preamble(c_file, obj): {{ size_t i; size_t len = {json_api.array_len('tmp')}; - {json_api.VAL_TYPE} *values = {json_api.array_values('tmp')}; ret->{obj.fixname}_len = len; ''', indent=1) calloc_with_check(c_file, f'ret->{obj.fixname}', 'len + 1', f'*ret->{obj.fixname}', indent=3) @@ -304,7 +302,7 @@ def emit_array_parse_preamble(c_file, obj): calloc_with_check(c_file, f'ret->{obj.fixname}_item_lens', 'len + 1', 'size_t', indent=3) -def emit_array_gen_preamble(c_file, obj, len_indent=' '): +def emit_array_gen_preamble(c_file, obj, len_indent=' '): """Emit the common preamble for array generation. Emits the if-OPT_GEN + gen_key + len setup + beautify_off + array_open + @@ -440,7 +438,7 @@ def emit_read_value(self, c_file, src, dest, keyname, obj_typename, level=1): {json_api.VAL_TYPE} val = {src}; if (val != NULL) {{ - char *str = {json_api.get_string('val')}; + const char *str = {json_api.get_string('val')}; {dest} = strdup (str ? str : ""); if ({dest} == NULL) return NULL; @@ -499,15 +497,6 @@ def emit_read_value(self, c_file, src, dest, keyname, obj_typename, level=1): emit(c_file, f''' {dest}_present = 1; }} - else - {{ - val = {src.replace(json_api.TYPE_TRUE, json_api.TYPE_FALSE)}; - if (val != NULL) - {{ - {dest} = 0; - {dest}_present = 1; - }} - }} ''', indent=level + 1) else: emit(c_file, f''' @@ -563,17 +552,6 @@ def emit_read_value(self, c_file, src, dest, keyname, obj_typename, level=1): return NULL; *({dest}) = {json_api.is_true('val')}; }} - else - {{ - val = get_val (tree, "{keyname}", {json_api.TYPE_FALSE}); - if (val != NULL) - {{ - {dest} = calloc (1, sizeof (bool)); - if ({dest} == NULL) - return NULL; - *({dest}) = {json_api.is_true('val')}; - }} - }} ''', indent=level) def emit_json_value(self, c_file, src, dst, ptx, level=1): @@ -802,58 +780,65 @@ def emit_make_body(self, c_file, obj, prefix): ''', indent=1) if obj.children is not None: - # O(n^2) complexity, but the objects should not really be big... condition = "\n && ".join( \ - [f'strcmp ({json_api.object_key_direct("tree", "i")}, "{i.origname}")' for i in obj.children]) + [f'strcmp (key_str, "{i.origname}")' for i in obj.children]) emit(c_file, f''' - if ({json_api.object_type_field("tree")} == {json_api.TYPE_OBJECT}) + if ({json_api.object_check('tree')}) {{ - size_t i; - size_t j = 0; - size_t cnt = {json_api.object_len_field("tree")}; - {json_api.VAL_TYPE} resi = NULL; - - if (ctx->options & OPT_PARSE_FULLKEY) + yyjson_obj_iter iter; + yyjson_obj_iter_init (tree, &iter); + yyjson_val *key; + yyjson_mut_doc *residual_doc = NULL; + yyjson_mut_val *residual_obj = NULL; + size_t unknown_count = 0; + + while ((key = yyjson_obj_iter_next (&iter)) != NULL) {{ - resi = calloc (1, sizeof(*tree)); - if (resi == NULL) - return NULL; - - {json_api.set_object_type("resi")}; - {json_api.alloc_object_keys("resi", "cnt")}; - if ({json_api.object_keys_field("resi")} == NULL) + const char *key_str = yyjson_get_str (key); + if (key_str != NULL + && {condition}) {{ - {json_api.tree_free("resi")}; - return NULL; - }} - {json_api.alloc_object_values("resi", "cnt")}; - if ({json_api.object_values_field("resi")} == NULL) - {{ - {json_api.tree_free("resi")}; - return NULL; - }} - }} - - for (i = 0; i < {json_api.object_len_field("tree")}; i++) - {{ - if ({condition}){{ + unknown_count++; if (ctx->options & OPT_PARSE_FULLKEY) {{ - {json_api.object_key_direct("resi", "j")} = {json_api.object_key_direct("tree", "i")}; - {json_api.object_key_direct("tree", "i")} = NULL; - {json_api.object_value_direct("resi", "j")} = {json_api.object_value_direct("tree", "i")}; - {json_api.object_value_direct("tree", "i")} = NULL; - {json_api.object_len_field("resi")}++; + if (residual_doc == NULL) + {{ + residual_doc = yyjson_mut_doc_new (NULL); + if (residual_doc == NULL) + return NULL; + residual_obj = yyjson_mut_obj (residual_doc); + if (residual_obj == NULL) + {{ + yyjson_mut_doc_free (residual_doc); + return NULL; + }} + yyjson_mut_doc_set_root (residual_doc, residual_obj); + }} + {{ + yyjson_val *rval = yyjson_obj_iter_get_val (key); + yyjson_mut_val *mut_key = yyjson_val_mut_copy (residual_doc, key); + yyjson_mut_val *mut_val = yyjson_val_mut_copy (residual_doc, rval); + if (mut_key == NULL || mut_val == NULL) + {{ + yyjson_mut_doc_free (residual_doc); + return NULL; + }} + yyjson_mut_obj_add (residual_obj, mut_key, mut_val); + }} }} - j++; }} }} - if ((ctx->options & OPT_PARSE_STRICT) && j > 0 && ctx->errfile != NULL) + if ((ctx->options & OPT_PARSE_STRICT) && unknown_count > 0 && ctx->errfile != NULL) (void) fprintf (ctx->errfile, "WARNING: unknown key found\\n"); - if (ctx->options & OPT_PARSE_FULLKEY) - ret->_residual = resi; + if ((ctx->options & OPT_PARSE_FULLKEY) && residual_doc != NULL) + {{ + ret->_residual = yyjson_mut_write (residual_doc, 0, NULL); + yyjson_mut_doc_free (residual_doc); + if (ret->_residual == NULL) + return NULL; + }} }} ''', indent=1) @@ -893,7 +878,7 @@ def emit_free_body(self, c_file, obj, prefix): if obj.children is not None: emit(c_file, f''' - {json_api.tree_free('ptr->_residual')}; + free (ptr->_residual); ptr->_residual = NULL; ''', indent=1) @@ -990,8 +975,8 @@ def emit_make_body(self, c_file, obj, prefix): {{ size_t i; size_t len = {json_api.object_len('tree')}; - const char **keys = {json_api.object_keys('tree')}; - {json_api.VAL_TYPE} *values = {json_api.object_values('tree')}; + yyjson_obj_iter iter; + yyjson_obj_iter_init (tree, &iter); ret->len = len; ''', indent=1) @@ -1001,22 +986,22 @@ def emit_make_body(self, c_file, obj, prefix): emit(c_file, f''' for (i = 0; i < len; i++) {{ - {json_api.VAL_TYPE} val; - const char *tmpkey = keys[i]; + yyjson_val *key = yyjson_obj_iter_next (&iter); + yyjson_val *val = yyjson_obj_iter_get_val (key); + const char *tmpkey = yyjson_get_str (key); ret->keys[i] = strdup (tmpkey ? tmpkey : ""); ''', indent=2) null_check_return(c_file, 'ret->keys[i]', indent=3) emit(c_file, f''' - val = values[i]; ret->{child.fixname}[i] = make_{childname} (val, ctx, err); ''', indent=3) null_check_return(c_file, f'ret->{child.fixname}[i]', indent=3) - c_file.append(' }\n') c_file.append(' }\n') + c_file.append(' }\n') def emit_gen_body(self, c_file, obj, prefix): """Generate the body of gen_typename() for mapStringObject.""" @@ -1155,10 +1140,9 @@ def emit_parse(self, c_file, obj, prefix, obj_typename, indent=1): ''', indent=indent) emit_value_error(c_file, obj.origname, indent=indent + 3) emit(c_file, ''' - } } } - while (0); + } while (0); ''', indent=indent) def emit_generate(self, c_file, obj, prefix, indent=1): @@ -1237,7 +1221,7 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): emit(c_file, f''' for (i = 0; i < len; i++) {{ - {json_api.VAL_TYPE} val = values[i]; + {json_api.VAL_TYPE} val = {json_api.array_get('tmp', 'i')}; ''', indent=3) if obj.nested_array: @@ -1247,12 +1231,11 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): ''', indent=4) null_check_return(c_file, f'ret->{obj.fixname}[i]', indent=4) emit(c_file, f''' - {json_api.VAL_TYPE} *items = {json_api.array_values('val')}; for (j = 0; j < {json_api.array_len('val')}; j++) {{ ''', indent=4) emit(c_file, f''' - ret->{obj.fixname}[i][j] = make_{typename} (items[j], ctx, err); + ret->{obj.fixname}[i][j] = make_{typename} ({json_api.array_get('val', 'j')}, ctx, err); ''', indent=5) null_check_return(c_file, f'ret->{obj.fixname}[i][j]', indent=5) emit(c_file, f''' @@ -1266,10 +1249,9 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): null_check_return(c_file, f'ret->{obj.fixname}[i]', indent=4) emit(c_file, ''' - } - } - } - while (0); + } + } + } while (0); ''', indent=1) def emit_generate(self, c_file, obj, prefix): @@ -1397,7 +1379,6 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): if obj.nested_array: emit(c_file, f''' - {json_api.VAL_TYPE} *items = {json_api.array_values('tmp')}; ret->{obj.fixname}_len = {json_api.array_len('tmp')}; ret->{obj.fixname} = calloc (ret->{obj.fixname}_len + 1, sizeof (*ret->{obj.fixname})); ''', indent=4) @@ -1406,7 +1387,7 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): size_t j; for (j = 0; j < ret->{obj.fixname}_len; j++) {{ - char *str = {json_api.get_string('items[j]')}; + const char *str = {json_api.get_string(json_api.array_get('tmp', 'j'))}; ''', indent=4) emit(c_file, f''' ret->{obj.fixname}[j] = (uint8_t *)strdup (str ? str : ""); @@ -1417,7 +1398,7 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): ''', indent=5) else: emit(c_file, f''' - char *str = {json_api.get_string('tmp')}; + const char *str = {json_api.get_string('tmp')}; ''', indent=3) emit(c_file, f''' ret->{obj.fixname} = (uint8_t *)strdup (str ? str : ""); @@ -1428,9 +1409,8 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): ''', indent=3) emit(c_file, ''' - } - } - while (0); + } + } while (0); ''', indent=1) def emit_generate(self, c_file, obj, prefix): @@ -1499,30 +1479,29 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): if obj.nested_array: emit(c_file, f''' - {json_api.VAL_TYPE} *items = {json_api.array_values('values[i]')}; - ret->{obj.fixname}[i] = calloc ( {json_api.array_len('values[i]')} + 1, sizeof (**ret->{obj.fixname})); + {json_api.VAL_TYPE} inner_arr = {json_api.array_get('tmp', 'i')}; + ret->{obj.fixname}[i] = calloc ( {json_api.array_len('inner_arr')} + 1, sizeof (**ret->{obj.fixname})); ''', indent=4) null_check_return(c_file, f'ret->{obj.fixname}[i]', indent=5) emit(c_file, f''' size_t j; - for (j = 0; j < {json_api.array_len('values[i]')}; j++) + for (j = 0; j < {json_api.array_len('inner_arr')}; j++) {{ ''', indent=4) - read_val_generator(c_file, 5, 'items[j]', + read_val_generator(c_file, 5, f'{json_api.array_get("inner_arr", "j")}', f"ret->{obj.fixname}[i][j]", obj.subtyp, obj.origname, obj_typename) emit(c_file, f''' ret->{obj.fixname}_item_lens[i] += 1; }}; ''', indent=5) else: - read_val_generator(c_file, 4, 'values[i]', + read_val_generator(c_file, 4, f'{json_api.array_get("tmp", "i")}', f"ret->{obj.fixname}[i]", obj.subtyp, obj.origname, obj_typename) emit(c_file, ''' - } - } - } - while (0); + } + } + } while (0); ''', indent=1) def emit_generate(self, c_file, obj, prefix): @@ -1668,14 +1647,13 @@ def emit_parse(self, c_file, obj, prefix, obj_typename): emit(c_file, f''' for (i = 0; i < len; i++) {{ - {json_api.VAL_TYPE} val = values[i]; + {json_api.VAL_TYPE} val = {json_api.array_get('tmp', 'i')}; ret->{obj.fixname}[i] = make_{map_func} (val, ctx, err); if (ret->{obj.fixname}[i] == NULL) return NULL; }} }} - }} - while (0); + }} while (0); ''', indent=1) def emit_generate(self, c_file, obj, prefix): @@ -1930,12 +1908,15 @@ def parse_json_to_c(obj, c_file, prefix): return emit(c_file, f''' define_cleaner_function ({typename} *, free_{typename}) + ''', indent=0) + c_file.append("\n") + emit(c_file, f''' {typename} * - make_{typename} ({json_api.VAL_TYPE} tree, const struct parser_context *ctx, parser_error *err) + make_{typename} ({json_api.VAL_TYPE}tree, const struct parser_context *ctx, parser_error *err) {{ - __auto_cleanup(free_{typename}) {typename} *ret = NULL; + __auto_cleanup (free_{typename}) {typename} *ret = NULL; *err = NULL; - (void) ctx; /* Silence compiler warning. */ + (void) ctx; /* Silence compiler warning. */ if (tree == NULL) return NULL; ret = calloc (1, sizeof (*ret)); @@ -1947,7 +1928,7 @@ def parse_json_to_c(obj, c_file, prefix): if handler and hasattr(handler, 'emit_make_body'): handler.emit_make_body(c_file, obj, prefix) - c_file.append(" return move_ptr (ret);\n") + c_file.append(" return move_ptr (ret);\n") c_file.append("}\n") c_file.append("\n") @@ -1972,14 +1953,14 @@ def get_c_json(obj, c_file, prefix): {{ {json_api.GEN_STATUS_TYPE} stat = {json_api.GEN_STATUS_OK}; *err = NULL; - (void) ptr; /* Silence compiler warning. */ + (void) ptr; /* Silence compiler warning. */ ''', indent=0) handler = get_type_handler(obj.typ) if handler and hasattr(handler, 'emit_gen_body'): handler.emit_gen_body(c_file, obj, prefix) - c_file.append(f" return {json_api.GEN_STATUS_OK};\n") + c_file.append(f" return {json_api.GEN_STATUS_OK};\n") c_file.append("}\n") c_file.append("\n") @@ -2011,7 +1992,7 @@ def make_clone(obj, c_file, prefix): {typename} * clone_{typename} ({typename} *src) {{ - __auto_cleanup(free_{typename}) {typename} *ret = NULL; + __auto_cleanup (free_{typename}) {typename} *ret = NULL; if (src == NULL) return NULL; @@ -2025,7 +2006,7 @@ def make_clone(obj, c_file, prefix): if handler and hasattr(handler, 'emit_clone_body'): handler.emit_clone_body(c_file, obj, prefix) - c_file.append(" return move_ptr (ret);\n") + c_file.append(" return move_ptr (ret);\n") c_file.append("}\n") c_file.append("\n") @@ -2065,10 +2046,9 @@ def make_c_free (obj, c_file, prefix): handler.emit_free_body(c_file, obj, prefix) emit(c_file, ''' - free (ptr); - } - + free (ptr); ''', indent=1) + c_file.append("}\n") def src_reflect(structs, schema_info, c_file, root_typ): @@ -2081,7 +2061,7 @@ def src_reflect(structs, schema_info, c_file, root_typ): /* Generated from {schema_info.name.basename}. Do not edit! */ #ifndef _GNU_SOURCE - #define _GNU_SOURCE + # define _GNU_SOURCE #endif #include #include @@ -2107,15 +2087,18 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): emit(c_file, f''' define_cleaner_function ({typename} *, free_{typename}) + ''', indent=0) + c_file.append("\n") + emit(c_file, f''' {typename} - *make_{typename} ({json_api.VAL_TYPE} tree, const struct parser_context *ctx, parser_error *err) + *make_{typename} ({json_api.VAL_TYPE}tree, const struct parser_context *ctx, parser_error *err) {{ - __auto_cleanup(free_{typename}) {typename} *ptr = NULL; + __auto_cleanup (free_{typename}) {typename} *ptr = NULL; size_t i, alen; (void) ctx; - if (tree == NULL || err == NULL || !({json_api.array_check('tree')})) + if (tree == NULL || err == NULL || ! ({json_api.array_check('tree')})) return NULL; *err = NULL; alen = {json_api.array_len('tree')}; @@ -2124,7 +2107,7 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): ptr = calloc (1, sizeof ({typename})); if (ptr == NULL) return NULL; - ptr->items = calloc (alen + 1, sizeof(*ptr->items)); + ptr->items = calloc (alen + 1, sizeof (*ptr->items)); if (ptr->items == NULL) return NULL; ptr->len = alen; @@ -2156,10 +2139,9 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): ptr->items[i] = calloc ( {json_api.array_len('work')} + 1, sizeof (**ptr->items)); if (ptr->items[i] == NULL) return NULL; - {json_api.VAL_TYPE} *tmps = {json_api.array_values('work')}; for (j = 0; j < {json_api.array_len('work')}; j++) {{ - ptr->items[i][j] = make_{subtypename} (tmps[j], ctx, err); + ptr->items[i][j] = make_{subtypename} ({json_api.array_get('work', 'j')}, ctx, err); if (ptr->items[i][j] == NULL) return NULL; ptr->subitem_lens[i] += 1; @@ -2174,14 +2156,14 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): elif obj.subtyp == 'byte': if obj.nested_array: emit(c_file, f''' - char *str = {json_api.get_string('work')}; + const char *str = {json_api.get_string('work')}; ptr->items[j] = (uint8_t *)strdup (str ? str : ""); if (ptr->items[j] == NULL) return NULL; ''', indent=2) else: emit(c_file, f''' - char *str = {json_api.get_string('tree')}; + const char *str = {json_api.get_string('tree')}; memcpy(ptr->items, str ? str : "", strlen(str ? str : "")); break; ''', indent=2) @@ -2192,11 +2174,10 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): if (ptr->items[i] == NULL) return NULL; size_t j; - {json_api.VAL_TYPE} *tmps = {json_api.array_values('work')}; for (j = 0; j < {json_api.array_len('work')}; j++) {{ ''', indent=2) - read_val_generator(c_file, 3, 'tmps[j]', \ + read_val_generator(c_file, 3, f'{json_api.array_get("work", "j")}', \ "ptr->items[i][j]", obj.subtyp, obj.origname, c_typ) emit(c_file, ''' ptr->subitem_lens[i] += 1; @@ -2210,7 +2191,7 @@ def get_c_epilog_for_array_make_parse(c_file, prefix, typ, obj): } ''', indent=1) - c_file.append(" return move_ptr(ptr);\n") + c_file.append(" return move_ptr (ptr);\n") c_file.append("}\n") c_file.append("\n") @@ -2455,7 +2436,7 @@ def get_c_epilog_for_array_make_gen(c_file, prefix, typ, obj): if (stat != {json_api.GEN_STATUS_OK}) GEN_SET_ERROR_AND_RETURN (stat, err); ''', indent=1) - c_file.append(f" return {json_api.GEN_STATUS_OK};\n") + c_file.append(f" return {json_api.GEN_STATUS_OK};\n") c_file.append("}\n") c_file.append("\n") @@ -2531,13 +2512,16 @@ def get_c_epilog(c_file, prefix, typ, obj): emit(c_file, f''' {json_api.get_val_cleaner_define()} + ''', indent=0) + c_file.append("\n") + emit(c_file, f''' {typename} * {typename}_parse_data (const char *jsondata, const struct parser_context *ctx, parser_error *err) {{ {typename} *ptr = NULL; - __auto_cleanup({json_api.TREE_FREE_FUNC}) {json_api.VAL_TYPE} tree = NULL; - char errbuf[1024]; + __auto_cleanup ({json_api.DOC_FREE_FUNC}) {json_api.DOC_TYPE}doc = NULL; + {json_api.VAL_TYPE}tree = NULL; struct parser_context tmp_ctx = {{ 0 }}; if (jsondata == NULL || err == NULL) @@ -2545,13 +2529,18 @@ def get_c_epilog(c_file, prefix, typ, obj): *err = NULL; if (ctx == NULL) - ctx = (const struct parser_context *)(&tmp_ctx); + ctx = (const struct parser_context *) (&tmp_ctx); - tree = {json_api.tree_parse('jsondata', 'errbuf', 'sizeof (errbuf)')}; + doc = {json_api.doc_read('jsondata', 'strlen (jsondata)')}; + if (doc == NULL) + {{ + *err = strdup ("cannot parse the data"); + return NULL; + }} + tree = {json_api.doc_get_root('doc')}; if (tree == NULL) {{ - if (asprintf (err, "cannot parse the data: %s", errbuf) < 0) - *err = strdup ("error allocating memory"); + *err = strdup ("cannot parse the data"); return NULL; }} ptr = make_{typename} (tree, ctx, err); @@ -2560,15 +2549,15 @@ def get_c_epilog(c_file, prefix, typ, obj): ''', indent=0) c_file.append(json_api.get_gen_cleanup_block()) + c_file.append("\n") emit(c_file, f''' - char * {typename}_generate_json (const {typename} *ptr, const struct parser_context *ctx, parser_error *err) {{ - __auto_cleanup(cleanup_{json_api.GEN_TYPE}) {json_api.GEN_TYPE} g = NULL; + __auto_cleanup (cleanup_{json_api.GEN_TYPE_NAME}) {json_api.GEN_TYPE}g = NULL; struct parser_context tmp_ctx = {{ 0 }}; - const unsigned char *gen_buf = NULL; + const char *gen_buf = NULL; char *json_buf = NULL; size_t gen_len = 0; @@ -2577,9 +2566,9 @@ def get_c_epilog(c_file, prefix, typ, obj): *err = NULL; if (ctx == NULL) - ctx = (const struct parser_context *)(&tmp_ctx); + ctx = (const struct parser_context *) (&tmp_ctx); - if (!json_gen_init(&g, ctx)) + if (! json_gen_init (&g, ctx)) {{ *err = strdup ("Json_gen init failed"); return json_buf; diff --git a/yajl b/yajl deleted file mode 160000 index 6bc52193..00000000 --- a/yajl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6bc5219389fd2752631682b0a8368e6d8218a8c5 diff --git a/yyjson b/yyjson new file mode 160000 index 00000000..8e9920f4 --- /dev/null +++ b/yyjson @@ -0,0 +1 @@ +Subproject commit 8e9920f47c77e69ce704db6c3040961d821c2fea From 5c1b1269e3061d651a7a6dd9d1dd6ce22184a1d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 16 Apr 2026 09:23:11 +0000 Subject: [PATCH 4/5] tests: add test-15 for int64 and uint64 value parsing Test parsing and round-trip generation of int64 and uint64 fields with boundary values: INT64_MAX, INT64_MIN, UINT64_MAX, values exceeding 32-bit range, negative values, and zero. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano --- Makefile.am | 9 +- tests/data/int64_values.json | 18 +++ tests/test-15.c | 130 +++++++++++++++++++ tests/test-spec/basic/test_int64_values.json | 55 ++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 tests/data/int64_values.json create mode 100644 tests/test-15.c create mode 100644 tests/test-spec/basic/test_int64_values.json diff --git a/Makefile.am b/Makefile.am index c2b3e7c8..44fcd8e2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -50,6 +50,7 @@ SOURCE_FILES = \ src/ocispec/basic_test_top_double_array_obj.c \ src/ocispec/basic_test_top_double_array_refobj.c \ src/ocispec/basic_test_top_double_array_string.c \ + src/ocispec/basic_test_int64_values.c \ src/ocispec/basic_test_map_string_string_array.c HEADER_FILES = $(SOURCE_FILES:.c=.h) @@ -144,6 +145,8 @@ src/ocispec/basic_test_double_array_item.h \ src/ocispec/basic_test_top_double_array_obj.c \ src/ocispec/basic_test_top_double_array_refobj.c \ src/ocispec/basic_test_top_double_array_string.c \ + src/ocispec/basic_test_int64_values.h \ + src/ocispec/basic_test_int64_values.c \ src/ocispec/basic_test_map_string_string_array.c: src/basic-test_stamp $(HEADER_FILES): %.h: %.c src/ocispec/generate.py @@ -222,6 +225,9 @@ tests_test_13_LDADD = $(TESTS_LDADD) tests_test_14_SOURCES = tests/test-14.c tests_test_14_LDADD = $(TESTS_LDADD) +tests_test_15_SOURCES = tests/test-15.c +tests_test_15_LDADD = $(TESTS_LDADD) + src_ocispec_validate_SOURCES = src/ocispec/validate.c src_ocispec_validate_LDADD = $(TESTS_LDADD) @@ -238,7 +244,8 @@ TESTS = tests/test-1 \ tests/test-11 \ tests/test-12 \ tests/test-13 \ - tests/test-14 + tests/test-14 \ + tests/test-15 noinst_PROGRAMS = src/ocispec/validate $(TESTS) diff --git a/tests/data/int64_values.json b/tests/data/int64_values.json new file mode 100644 index 00000000..ec2ca61c --- /dev/null +++ b/tests/data/int64_values.json @@ -0,0 +1,18 @@ +{ + "positive_int64": 5000000000000000000, + "negative_int64": -5000000000000000000, + "max_int64": 9223372036854775807, + "min_int64": -9223372036854775808, + "zero_int64": 0, + "max_int64_minus1": 9223372036854775806, + "min_int64_plus1": -9223372036854775807, + "small_uint64": 42, + "large_uint64": 10000000000000000000, + "max_uint64": 18446744073709551615, + "max_uint64_minus1": 18446744073709551614, + "zero_uint64": 0, + "one_int64": 1, + "neg_one_int64": -1, + "one_uint64": 1, + "neg_two_int64": -2 +} diff --git a/tests/test-15.c b/tests/test-15.c new file mode 100644 index 00000000..9de29ce0 --- /dev/null +++ b/tests/test-15.c @@ -0,0 +1,130 @@ +/* Copyright (C) 2026 Giuseppe Scrivano + +libocispec is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +libocispec is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with libocispec. If not, see . + +*/ + +#include "config.h" +#include +#include +#include +#include +#include "ocispec/basic_test_int64_values.h" + +#define CHECK_INT64(field, expected) do { \ + if (data->field != (expected)) { \ + printf (#field ": expected %" PRId64 ", got %" PRId64 "\n", (int64_t)(expected), data->field); \ + exit (1); \ + } \ + } while (0) + +#define CHECK_UINT64(field, expected) do { \ + if (data->field != (expected)) { \ + printf (#field ": expected %" PRIu64 ", got %" PRIu64 "\n", (uint64_t)(expected), data->field); \ + exit (1); \ + } \ + } while (0) + +#define CHECK_ROUNDTRIP_INT64(field) do { \ + if (data2->field != data->field) { \ + printf ("round-trip " #field ": expected %" PRId64 ", got %" PRId64 "\n", data->field, data2->field); \ + exit (1); \ + } \ + } while (0) + +#define CHECK_ROUNDTRIP_UINT64(field) do { \ + if (data2->field != data->field) { \ + printf ("round-trip " #field ": expected %" PRIu64 ", got %" PRIu64 "\n", data->field, data2->field); \ + exit (1); \ + } \ + } while (0) + +int +main () +{ + parser_error err = NULL; + struct parser_context ctx = { 0 }; + basic_test_int64_values *data; + basic_test_int64_values *data2; + char *json_buf = NULL; + + data = basic_test_int64_values_parse_file ("tests/data/int64_values.json", &ctx, &err); + if (data == NULL) + { + printf ("parse error: %s\n", err); + free (err); + return 1; + } + + /* int64 checks. */ + CHECK_INT64 (positive_int64, 5000000000000000000LL); + CHECK_INT64 (negative_int64, -5000000000000000000LL); + CHECK_INT64 (max_int64, INT64_MAX); + CHECK_INT64 (min_int64, INT64_MIN); + CHECK_INT64 (zero_int64, 0); + CHECK_INT64 (max_int64_minus1, INT64_MAX - 1); + CHECK_INT64 (min_int64_plus1, INT64_MIN + 1); + CHECK_INT64 (one_int64, 1); + CHECK_INT64 (neg_one_int64, -1); + CHECK_INT64 (neg_two_int64, -2); + + /* uint64 checks. */ + CHECK_UINT64 (small_uint64, 42); + CHECK_UINT64 (large_uint64, 10000000000000000000ULL); + CHECK_UINT64 (max_uint64, UINT64_MAX); + CHECK_UINT64 (max_uint64_minus1, UINT64_MAX - 1); + CHECK_UINT64 (zero_uint64, 0); + CHECK_UINT64 (one_uint64, 1); + + /* Round-trip: generate JSON, re-parse, and verify. */ + json_buf = basic_test_int64_values_generate_json (data, &ctx, &err); + if (json_buf == NULL) + { + printf ("generate error: %s\n", err); + free (err); + exit (1); + } + + data2 = basic_test_int64_values_parse_data (json_buf, &ctx, &err); + if (data2 == NULL) + { + printf ("re-parse error: %s\n", err); + free (err); + exit (1); + } + + CHECK_ROUNDTRIP_INT64 (positive_int64); + CHECK_ROUNDTRIP_INT64 (negative_int64); + CHECK_ROUNDTRIP_INT64 (max_int64); + CHECK_ROUNDTRIP_INT64 (min_int64); + CHECK_ROUNDTRIP_INT64 (zero_int64); + CHECK_ROUNDTRIP_INT64 (max_int64_minus1); + CHECK_ROUNDTRIP_INT64 (min_int64_plus1); + CHECK_ROUNDTRIP_INT64 (one_int64); + CHECK_ROUNDTRIP_INT64 (neg_one_int64); + CHECK_ROUNDTRIP_INT64 (neg_two_int64); + + CHECK_ROUNDTRIP_UINT64 (small_uint64); + CHECK_ROUNDTRIP_UINT64 (large_uint64); + CHECK_ROUNDTRIP_UINT64 (max_uint64); + CHECK_ROUNDTRIP_UINT64 (max_uint64_minus1); + CHECK_ROUNDTRIP_UINT64 (zero_uint64); + CHECK_ROUNDTRIP_UINT64 (one_uint64); + + free (json_buf); + free_basic_test_int64_values (data); + free_basic_test_int64_values (data2); + + return 0; +} diff --git a/tests/test-spec/basic/test_int64_values.json b/tests/test-spec/basic/test_int64_values.json new file mode 100644 index 00000000..075eee0a --- /dev/null +++ b/tests/test-spec/basic/test_int64_values.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "int64 values", + "type": "object", + "properties": { + "positive_int64": { + "type": "int64" + }, + "negative_int64": { + "type": "int64" + }, + "max_int64": { + "type": "int64" + }, + "min_int64": { + "type": "int64" + }, + "zero_int64": { + "type": "int64" + }, + "max_int64_minus1": { + "type": "int64" + }, + "min_int64_plus1": { + "type": "int64" + }, + "small_uint64": { + "type": "uint64" + }, + "large_uint64": { + "type": "uint64" + }, + "max_uint64": { + "type": "uint64" + }, + "max_uint64_minus1": { + "type": "uint64" + }, + "zero_uint64": { + "type": "uint64" + }, + "one_int64": { + "type": "int64" + }, + "neg_one_int64": { + "type": "int64" + }, + "one_uint64": { + "type": "uint64" + }, + "neg_two_int64": { + "type": "int64" + } + } +} From 4bf88963c81d90ca53858c8dbe5cca7b5d701505 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Thu, 16 Apr 2026 10:26:38 +0000 Subject: [PATCH 5/5] fuzzing: add multi-mode round-trip fuzzer and CI job Expand the fuzzer from a single-mode runtime config parser to 7 modes covering all OCI spec parsers with round-trip testing (parse, generate, re-parse). Add honggfuzz-based CI fuzzing with Docker, matching the pattern used by crun. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano --- .github/workflows/test.yaml | 11 ++ Makefile.am | 1 + src/ocispec/validate.c | 327 ++++++++++++++++++++++++++++++++++-- tests/fuzzing/Dockerfile | 12 ++ tests/fuzzing/run-tests.sh | 34 ++++ 5 files changed, 373 insertions(+), 12 deletions(-) create mode 100644 tests/fuzzing/Dockerfile create mode 100755 tests/fuzzing/run-tests.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b2ca8d47..192ccde9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -53,6 +53,17 @@ jobs: git describe --broken --dirty --all | grep -qv dirty make clean + fuzzing: + runs-on: ubuntu-latest + name: Fuzzing + steps: + - uses: actions/checkout@v3.0.2 + with: + submodules: true + set-safe-directory: true + - run: sudo docker build -t libocispec-fuzzing tests/fuzzing + - run: sudo docker run --rm -e RUN_TIME=120 -v ${PWD}:/libocispec libocispec-fuzzing + test_and_build_rust_bindings: name: test and build rust bindings runs-on: ubuntu-latest diff --git a/Makefile.am b/Makefile.am index 44fcd8e2..9bc22cc1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -276,6 +276,7 @@ EXTRA_DIST = autogen.sh \ tests/data/top_double_array_string.json \ tests/data/config-netdevices.json \ tests/data/map-string-string-array.json \ + tests/data/int64_values.json \ tests/test-spec \ src/ocispec/generate.py \ src/ocispec/headers.py \ diff --git a/src/ocispec/validate.c b/src/ocispec/validate.c index 143bf62c..bf21d519 100644 --- a/src/ocispec/validate.c +++ b/src/ocispec/validate.c @@ -21,45 +21,348 @@ along with libocispec. If not, see . #include #include #include "ocispec/runtime_spec_schema_config_schema.h" +#include "ocispec/image_spec_schema_config_schema.h" +#include "ocispec/image_spec_schema_image_index_schema.h" +#include "ocispec/image_spec_schema_image_manifest_schema.h" +#include "ocispec/image_spec_schema_image_layout_schema.h" #ifdef FUZZER + +static int fuzzing_mode = -1; + int LLVMFuzzerInitialize (int *argc, char ***argv) { + const char *mode = getenv ("FUZZING_MODE"); + if (mode) + fuzzing_mode = atoi (mode); return 0; } -int -LLVMFuzzerTestOneInput (uint8_t *buf, size_t len) +static int +fuzz_runtime_config_strict (const uint8_t *buf, size_t len) { runtime_spec_schema_config_schema *container; - struct parser_context ctx; - parser_error err; - FILE *s; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; - if (len == 0) + FILE *s = fmemopen ((void *) buf, len, "r"); + if (s == NULL) return 0; - s = fmemopen (buf, len, "r"); + container = runtime_spec_schema_config_schema_parse_file_stream (s, &ctx, &err); + fclose (s); + + if (container) + { + json_buf = runtime_spec_schema_config_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + runtime_spec_schema_config_schema *reparsed; + reparsed = runtime_spec_schema_config_schema_parse_data (json_buf, &ctx, &err); + free_runtime_spec_schema_config_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_runtime_spec_schema_config_schema (container); + } + else + { + free (err); + } + return 0; +} + +static int +fuzz_runtime_config_fullkey (const uint8_t *buf, size_t len) +{ + runtime_spec_schema_config_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_FULLKEY, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; + + FILE *s = fmemopen ((void *) buf, len, "r"); if (s == NULL) return 0; - ctx.options = OPT_PARSE_STRICT; - ctx.errfile = stderr; container = runtime_spec_schema_config_schema_parse_file_stream (s, &ctx, &err); fclose (s); + if (container) { + json_buf = runtime_spec_schema_config_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + runtime_spec_schema_config_schema *reparsed; + reparsed = runtime_spec_schema_config_schema_parse_data (json_buf, &ctx, &err); + free_runtime_spec_schema_config_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } free_runtime_spec_schema_config_schema (container); - return 0; } - if (err) + else + { + free (err); + } + return 0; +} + +static int +fuzz_image_config (const uint8_t *buf, size_t len) +{ + image_spec_schema_config_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; + + FILE *s = fmemopen ((void *) buf, len, "r"); + if (s == NULL) + return 0; + + container = image_spec_schema_config_schema_parse_file_stream (s, &ctx, &err); + fclose (s); + + if (container) + { + json_buf = image_spec_schema_config_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + image_spec_schema_config_schema *reparsed; + reparsed = image_spec_schema_config_schema_parse_data (json_buf, &ctx, &err); + free_image_spec_schema_config_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_image_spec_schema_config_schema (container); + } + else + { + free (err); + } + return 0; +} + +static int +fuzz_image_index (const uint8_t *buf, size_t len) +{ + image_spec_schema_image_index_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; + + FILE *s = fmemopen ((void *) buf, len, "r"); + if (s == NULL) + return 0; + + container = image_spec_schema_image_index_schema_parse_file_stream (s, &ctx, &err); + fclose (s); + + if (container) + { + json_buf = image_spec_schema_image_index_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + image_spec_schema_image_index_schema *reparsed; + reparsed = image_spec_schema_image_index_schema_parse_data (json_buf, &ctx, &err); + free_image_spec_schema_image_index_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_image_spec_schema_image_index_schema (container); + } + else { - fprintf (stderr, "error: %s\n", err); free (err); } return 0; } + +static int +fuzz_image_manifest (const uint8_t *buf, size_t len) +{ + image_spec_schema_image_manifest_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; + + FILE *s = fmemopen ((void *) buf, len, "r"); + if (s == NULL) + return 0; + + container = image_spec_schema_image_manifest_schema_parse_file_stream (s, &ctx, &err); + fclose (s); + + if (container) + { + json_buf = image_spec_schema_image_manifest_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + image_spec_schema_image_manifest_schema *reparsed; + reparsed = image_spec_schema_image_manifest_schema_parse_data (json_buf, &ctx, &err); + free_image_spec_schema_image_manifest_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_image_spec_schema_image_manifest_schema (container); + } + else + { + free (err); + } + return 0; +} + +static int +fuzz_image_layout (const uint8_t *buf, size_t len) +{ + image_spec_schema_image_layout_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *json_buf = NULL; + + FILE *s = fmemopen ((void *) buf, len, "r"); + if (s == NULL) + return 0; + + container = image_spec_schema_image_layout_schema_parse_file_stream (s, &ctx, &err); + fclose (s); + + if (container) + { + json_buf = image_spec_schema_image_layout_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + image_spec_schema_image_layout_schema *reparsed; + reparsed = image_spec_schema_image_layout_schema_parse_data (json_buf, &ctx, &err); + free_image_spec_schema_image_layout_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_image_spec_schema_image_layout_schema (container); + } + else + { + free (err); + } + return 0; +} + +static int +fuzz_runtime_config_parse_data (const uint8_t *buf, size_t len) +{ + runtime_spec_schema_config_schema *container; + struct parser_context ctx = { .options = OPT_PARSE_STRICT, .errfile = stderr }; + parser_error err = NULL; + char *data; + + data = malloc (len + 1); + if (data == NULL) + return 0; + + memcpy (data, buf, len); + data[len] = '\0'; + + container = runtime_spec_schema_config_schema_parse_data (data, &ctx, &err); + free (data); + + if (container) + { + char *json_buf = runtime_spec_schema_config_schema_generate_json (container, &ctx, &err); + if (json_buf) + { + runtime_spec_schema_config_schema *reparsed; + reparsed = runtime_spec_schema_config_schema_parse_data (json_buf, &ctx, &err); + free_runtime_spec_schema_config_schema (reparsed); + free (err); + err = NULL; + free (json_buf); + } + else + { + free (err); + err = NULL; + } + free_runtime_spec_schema_config_schema (container); + } + else + { + free (err); + } + return 0; +} + +int +LLVMFuzzerTestOneInput (const uint8_t *buf, size_t len) +{ + if (len == 0) + return 0; + + switch (fuzzing_mode) + { + case 0: + return fuzz_runtime_config_strict (buf, len); + case 1: + return fuzz_runtime_config_fullkey (buf, len); + case 2: + return fuzz_image_config (buf, len); + case 3: + return fuzz_image_index (buf, len); + case 4: + return fuzz_image_manifest (buf, len); + case 5: + return fuzz_image_layout (buf, len); + case 6: + return fuzz_runtime_config_parse_data (buf, len); + default: + /* Run all modes. */ + fuzz_runtime_config_strict (buf, len); + fuzz_runtime_config_fullkey (buf, len); + fuzz_image_config (buf, len); + fuzz_image_index (buf, len); + fuzz_image_manifest (buf, len); + fuzz_image_layout (buf, len); + fuzz_runtime_config_parse_data (buf, len); + return 0; + } +} #endif #ifndef FUZZER diff --git a/tests/fuzzing/Dockerfile b/tests/fuzzing/Dockerfile new file mode 100644 index 00000000..c9a0a1ca --- /dev/null +++ b/tests/fuzzing/Dockerfile @@ -0,0 +1,12 @@ +FROM quay.io/fedora/fedora:latest + +RUN dnf install -y python3 automake autoconf libtool make git pkg-config clang \ + glibc-static libunwind-devel binutils-devel xz-devel libatomic + +RUN git clone --depth 1 https://github.com/google/honggfuzz.git && cd honggfuzz && make -j $(nproc) && make install PREFIX=/usr + +COPY run-tests.sh /usr/local/bin + +WORKDIR /libocispec + +ENTRYPOINT /usr/local/bin/run-tests.sh diff --git a/tests/fuzzing/run-tests.sh b/tests/fuzzing/run-tests.sh new file mode 100755 index 00000000..6c3036d3 --- /dev/null +++ b/tests/fuzzing/run-tests.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -xeu + +TIMEOUT=${TIMEOUT:=10} +RUN_TIME=${RUN_TIME:=600} +VERBOSITY=${VERBOSITY:=} + +N_TESTS=7 + +SINGLE_RUN_TIME=$(( RUN_TIME / N_TESTS )) + +git config --global --add safe.directory /libocispec +git clean -fdx +./autogen.sh +./configure --enable-embedded-yyjson HFUZZ_CC_UBSAN=1 HFUZZ_CC_ASAN=1 CC=hfuzz-clang CPPFLAGS="-D FUZZER" CFLAGS="-ggdb3 -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,indirect-calls" +make -j "$(nproc)" + +function run_test { + export FUZZING_MODE=$1 + + # shellcheck disable=SC2086 + result=$(honggfuzz --exit_upon_crash $VERBOSITY --run_time "$SINGLE_RUN_TIME" --timeout "$TIMEOUT" -T -i tests/data -- src/ocispec/validate 2>&1 | tail -n 2) + echo "$result" + echo "$result" | grep -q crashes_count:0 +} + +run_test 0 +run_test 1 +run_test 2 +run_test 3 +run_test 4 +run_test 5 +run_test 6