. "$BST_LIB" # cJSON is an ultralightweight JSON parser in ANSI C: one translation unit that # parses a document into a tree of cJSON nodes, walks and edits it through typed # accessors, and prints it back either formatted or minified. The port also ships # cJSON_Utils, the companion library implementing the three RFC-defined operations # over that same tree -- JSON Pointer (RFC 6901), JSON Patch (RFC 6902) and JSON # Merge Patch (RFC 7386) -- plus object sorting. # # The distro is static-only and cJSON has no CLI of its own, so the whole shipped # payload is two headers, two pkg-config files and the find_package(cJSON) cmake # files (-dev) plus the archives libcjson.a and libcjson_utils.a (-staticdev). # There is no consumer on the image and no on-target C compiler, so -- unlike popt # or lz4 -- no runtime register is available here: every case asserts the shipped # artifact, in the shape the header-and-archive libraries before it established. # # What keeps that from being a file listing is the completeness check below: it # reads EVERY function the installed header declares and requires each in the # archive, so a partial compile, a visibility mistake or a header/library version # skew all fail it, and the assertion tracks the header instead of a list here. # # --------------------------------------------------------------------------- # WHY THIS SUITE USES BUILTIN MATCHING INSTEAD OF check_out ... cat # # The first draft was written in the usual `check_out cat "$inc"` # style and was killed by the runner's 60s per-suite bound (BST_SUITE_TIMEOUT) # after 156 of its cases. Nothing was hanging: each check_out costs a command # substitution plus an exec, and ~200 cases is ~400 process spawns, which on the # wasm32-hwjs runtime is a minute of forking on its own. # # So the files are read ONCE into shell variables and every text assertion is a # `case` glob against them -- no fork per case -- and the archive symbol lookup # is a single batched grep whose result is then matched the same way. Same # assertions, same case ids, ~25 spawns instead of ~400. `has`/`hasfile` below # are deliberately local to this suite rather than added to bst.sh, so a hot # helper does not change behaviour under the other 78 suites. # --------------------------------------------------------------------------- inc=/usr/include/cjson/cJSON.h incu=/usr/include/cjson/cJSON_Utils.h pc=/usr/lib/pkgconfig/libcjson.pc pcu=/usr/lib/pkgconfig/libcjson_utils.pc lib=/usr/lib/libcjson.a libu=/usr/lib/libcjson_utils.a cm=/usr/lib/cmake/cJSON nl=' ' # hasfile -- ok iff is a non-empty regular file. # has -- ok iff contains the literal . # hasline -- ok iff has as a WHOLE line # (substring matching would let cJSON_ParseWithOpts # satisfy a lookup for cJSON_Parse). hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not in archive: $2" ;; esac; } hasfile cjson/header-present "$inc" hasfile cjson/uheader-present "$incu" h=$(cat "$inc" 2>/dev/null) hu=$(cat "$incu" 2>/dev/null) has cjson/header-guard "#define cJSON__h" "$h" has cjson/uheader-guard "#define cJSON_Utils__h" "$hu" # The header's own version triple. cJSON states its version in three macros # rather than one string, and the pkg-config and cmake files below must agree # with them -- that agreement is what makes 1.7.19 a fact about the port rather # than about one file. has cjson/version-major "#define CJSON_VERSION_MAJOR 1" "$h" has cjson/version-minor "#define CJSON_VERSION_MINOR 7" "$h" has cjson/version-patch "#define CJSON_VERSION_PATCH 19" "$h" # The node types, which are the vocabulary of the whole API: a parsed document is # a tree of these and every cJSON_Is* predicate is a test against one. They are a # bitfield, so the shifts are asserted as written -- a caller masking against a # changed layout would silently misclassify nodes. has cjson/type-invalid "#define cJSON_Invalid (0)" "$h" has cjson/type-false "#define cJSON_False (1 << 0)" "$h" has cjson/type-true "#define cJSON_True (1 << 1)" "$h" has cjson/type-null "#define cJSON_NULL (1 << 2)" "$h" has cjson/type-number "#define cJSON_Number (1 << 3)" "$h" has cjson/type-string "#define cJSON_String (1 << 4)" "$h" has cjson/type-array "#define cJSON_Array (1 << 5)" "$h" has cjson/type-object "#define cJSON_Object (1 << 6)" "$h" has cjson/type-raw "#define cJSON_Raw (1 << 7)" "$h" # The two modifier bits: a node that borrows its child or valuestring rather than # owning it, which is what makes the *Reference constructors safe to free. has cjson/type-isreference "#define cJSON_IsReference 256" "$h" has cjson/type-stringisconst "#define cJSON_StringIsConst 512" "$h" # The two recursion guards -- the library's answer to hostile input, nesting depth # and reference cycles -- so their values are part of what a caller relies on when # it parses untrusted JSON. has cjson/limit-nesting "#define CJSON_NESTING_LIMIT 1000" "$h" has cjson/limit-circular "#define CJSON_CIRCULAR_LIMIT 10000" "$h" # The public types a caller declares or dereferences: the node, the allocator hook # pair, and the int typedef the predicates return. has cjson/type-struct "typedef struct cJSON" "$h" has cjson/type-hooks "typedef struct cJSON_Hooks" "$h" has cjson/type-bool "typedef int cJSON_bool" "$h" # The node's own fields, which ARE the tree: a doubly-linked sibling list, a child # pointer, the type tag and the three value slots. cJSON_ArrayForEach walks # child/next directly, so these are API rather than implementation. has cjson/field-next "struct cJSON *next;" "$h" has cjson/field-prev "struct cJSON *prev;" "$h" has cjson/field-child "struct cJSON *child;" "$h" has cjson/field-type "int type;" "$h" has cjson/field-valuestring "char *valuestring;" "$h" has cjson/field-valueint "int valueint;" "$h" has cjson/field-valuedouble "double valuedouble;" "$h" has cjson/field-string "char *string;" "$h" has cjson/field-malloc-hook "malloc_fn" "$h" has cjson/field-free-hook "free_fn" "$h" # --- the API surface, layer by layer ----------------------------------------- # # Asserted against the header's declarations. Each needle is the function name # followed by '(' so a mention in a comment cannot satisfy it. api() { has "cjson/api-${1#cJSON_}" "$1(" "$h"; } apiu() { has "cjson/uapi-${1#cJSONUtils_}" "$1(" "$hu"; } # Parsing. The four entry points differ in whether the caller supplies a length # (the variant that makes cJSON safe on a buffer with no NUL) and whether it wants # the parse-end pointer and strict trailing-data checking. GetErrorPtr is the only # way to learn where a failed parse stopped. for f in cJSON_Parse cJSON_ParseWithLength cJSON_ParseWithOpts \ cJSON_ParseWithLengthOpts cJSON_GetErrorPtr; do api "$f"; done # Printing, in its four shapes -- allocate-and-format, allocate-unformatted, print # into a growable buffer, print into memory the caller owns -- plus Minify, which # rewrites JSON in place stripping whitespace and comments. for f in cJSON_Print cJSON_PrintUnformatted cJSON_PrintBuffered \ cJSON_PrintPreallocated cJSON_Minify; do api "$f"; done # Construction: one constructor per node type, the three reference constructors # that borrow instead of copy, and the four bulk array builders. for f in cJSON_CreateNull cJSON_CreateTrue cJSON_CreateFalse cJSON_CreateBool \ cJSON_CreateNumber cJSON_CreateString cJSON_CreateRaw cJSON_CreateArray \ cJSON_CreateObject cJSON_CreateStringReference cJSON_CreateObjectReference \ cJSON_CreateArrayReference cJSON_CreateIntArray cJSON_CreateFloatArray \ cJSON_CreateDoubleArray cJSON_CreateStringArray; do api "$f"; done # Access: array indexing and size, object lookup both ways, and the two typed # readers that return NULL/NaN rather than trapping on a wrong-type node. for f in cJSON_GetArraySize cJSON_GetArrayItem cJSON_GetObjectItem \ cJSON_GetObjectItemCaseSensitive cJSON_HasObjectItem \ cJSON_GetStringValue cJSON_GetNumberValue; do api "$f"; done # The type predicates -- one per node type plus Invalid -- which are how a caller # checks a node before reading its value slot. for f in cJSON_IsInvalid cJSON_IsFalse cJSON_IsTrue cJSON_IsBool cJSON_IsNull \ cJSON_IsNumber cJSON_IsString cJSON_IsArray cJSON_IsObject \ cJSON_IsRaw; do api "$f"; done # Mutation: attaching items to arrays and objects (including the reference forms # and the const-key form), and the AddXToObject helpers that create and attach in # one call. for f in cJSON_AddItemToArray cJSON_AddItemToObject cJSON_AddItemToObjectCS \ cJSON_AddItemReferenceToArray cJSON_AddItemReferenceToObject \ cJSON_InsertItemInArray cJSON_AddNullToObject cJSON_AddTrueToObject \ cJSON_AddFalseToObject cJSON_AddBoolToObject cJSON_AddNumberToObject \ cJSON_AddStringToObject cJSON_AddRawToObject cJSON_AddObjectToObject \ cJSON_AddArrayToObject; do api "$f"; done # Detach and delete: the pair that distinguishes unlinking a node (the caller now # owns it) from unlinking and freeing it. for f in cJSON_DetachItemViaPointer cJSON_DetachItemFromArray \ cJSON_DetachItemFromObject cJSON_DetachItemFromObjectCaseSensitive \ cJSON_DeleteItemFromArray cJSON_DeleteItemFromObject \ cJSON_DeleteItemFromObjectCaseSensitive cJSON_Delete; do api "$f"; done # Replacement, deep copy, structural comparison, and the two setters. for f in cJSON_ReplaceItemViaPointer cJSON_ReplaceItemInArray \ cJSON_ReplaceItemInObject cJSON_ReplaceItemInObjectCaseSensitive \ cJSON_Duplicate cJSON_Compare cJSON_SetValuestring \ cJSON_SetNumberHelper; do api "$f"; done # The allocator seam: a caller may route cJSON's allocations through its own # malloc/free, and must use cJSON_free to release what cJSON_Print returned. for f in cJSON_InitHooks cJSON_malloc cJSON_free cJSON_Version; do api "$f"; done # The four convenience macros. They are the API a caller writes most often -- # ArrayForEach in particular is the only supported way to iterate -- and being # macros they exist ONLY in the header, so nothing in the archive stands in for # them. for m in cJSON_SetIntValue cJSON_SetNumberValue cJSON_SetBoolValue \ cJSON_ArrayForEach; do has "cjson/macro-${m#cJSON_}" "#define $m" "$h" done # --- cJSON_Utils: the RFC layer ---------------------------------------------- # # Each of the three operations ships in a plain and a CaseSensitive form, which is # the whole shape of this library: the same algorithm over the case-sensitive or # case-insensitive object lookup. # JSON Pointer (RFC 6901): resolve a pointer to a node, and the inverse -- find # the pointer that names a node already in hand. for f in cJSONUtils_GetPointer cJSONUtils_GetPointerCaseSensitive \ cJSONUtils_FindPointerFromObjectTo; do apiu "$f"; done # JSON Patch (RFC 6902): apply a patch array, generate one from a document pair, # and append a single operation to a patch under construction. for f in cJSONUtils_ApplyPatches cJSONUtils_ApplyPatchesCaseSensitive \ cJSONUtils_GeneratePatches cJSONUtils_GeneratePatchesCaseSensitive \ cJSONUtils_AddPatchToArray; do apiu "$f"; done # JSON Merge Patch (RFC 7386): the other patch format, where the patch is a # document shaped like the target rather than a list of operations. for f in cJSONUtils_MergePatch cJSONUtils_MergePatchCaseSensitive \ cJSONUtils_GenerateMergePatch \ cJSONUtils_GenerateMergePatchCaseSensitive; do apiu "$f"; done # Object sorting, which is what makes GeneratePatches' output deterministic. for f in cJSONUtils_SortObject cJSONUtils_SortObjectCaseSensitive; do apiu "$f"; done # --- pkg-config --------------------------------------------------------------- # # Both files ship, and both are where a future recipe's PKG_CHECK_MODULES reads # the link line. libdir/includedir are asserted substituted rather than left as # @PLACEHOLDER@ text. hasfile cjson/pc-present "$pc" hasfile cjson/upc-present "$pcu" p=$(cat "$pc" 2>/dev/null) pu=$(cat "$pcu" 2>/dev/null) has cjson/pc-name "Name: libcjson" "$p" has cjson/pc-version "Version: 1.7.19" "$p" has cjson/pc-libs "-lcjson" "$p" has cjson/pc-private "Libs.private: -lm" "$p" has cjson/pc-cflags '-I${includedir}/cjson' "$p" has cjson/pc-libdir "libdir=/usr/lib" "$p" has cjson/upc-name "Name: libcjson_utils" "$pu" has cjson/upc-version "Version: 1.7.19" "$pu" has cjson/upc-libs "-lcjson_utils" "$pu" # cJSON_Utils cannot resolve without cJSON, and the .pc says so -- this is what # makes `pkg-config --libs libcjson_utils` emit both archives in the right order. has cjson/upc-requires "Requires: libcjson" "$pu" # --- cmake package files ------------------------------------------------------ # # The other integration route: find_package(cJSON). The version file must agree # with the header triple above, and the imported-target files are the one artifact # that proves the STATIC arm was selected -- on the stock recipe's shared build # these would name libcjson.so.1.7.19 instead. hasfile cjson/cmake-config "$cm/cJSONConfig.cmake" hasfile cjson/cmake-version "$cm/cJSONConfigVersion.cmake" hasfile cjson/cmake-targets "$cm/cjson.cmake" hasfile cjson/cmake-utargets "$cm/cjson_utils.cmake" cv=$(cat "$cm/cJSONConfigVersion.cmake" 2>/dev/null) cn=$(cat "$cm/cjson-noconfig.cmake" 2>/dev/null) cun=$(cat "$cm/cjson_utils-noconfig.cmake" 2>/dev/null) has cjson/cmake-pkgver 'PACKAGE_VERSION "1.7.19"' "$cv" has cjson/cmake-imports-archive 'IMPORTED_LOCATION_NOCONFIG "/usr/lib/libcjson.a"' "$cn" has cjson/cmake-uimports-archive 'IMPORTED_LOCATION_NOCONFIG "/usr/lib/libcjson_utils.a"' "$cun" # --- the archives ------------------------------------------------------------- hasfile cjson/lib-present "$lib" hasfile cjson/ulib-present "$libu" check_out cjson/lib-archive "!" head -c 8 "$lib" check_out cjson/ulib-archive "!" head -c 8 "$libu" # Flatten both archives to printable text. GNU grep suppresses matches inside data # it classifies as binary, and busybox tr does not handle [:print:] under -c, so # the octal range is the formulation both greps and both trs agree on -- derived # empirically by 69-libb64.test and reused since. t=${TMPDIR:-/tmp}/bst-cjson.$$ flat=$t.a; flatu=$t.ua; namef=$t.n; nameuf=$t.nu tr -c '\40-\176\n' ' ' < "$lib" > "$flat" 2>/dev/null tr -c '\40-\176\n' ' ' < "$libu" > "$flatu" 2>/dev/null # cJSON is one translation unit per library, so the member name is the whole # manifest: there is no partial-compile shape where some members are missing. memb=$(grep -o 'cJSON[_A-Za-z]*\.c\.o' "$flat" 2>/dev/null | head -1) membu=$(grep -o 'cJSON[_A-Za-z]*\.c\.o' "$flatu" 2>/dev/null | head -1) check_eq cjson/member-cjson "cJSON.c.o" "$memb" check_eq cjson/member-cutils "cJSON_Utils.c.o" "$membu" # Every function the INSTALLED header declares must be defined in the archive. The # list is read off the header ON THE TARGET rather than hard-coded here, so this # asserts the two halves of the port against each other: a symbol dropped by the # wasm32 compile, hidden by a visibility flag, or introduced by a header the # archive was not built from all show up as a miss. # # cJSON's declarations are uniformly `CJSON_PUBLIC(type) name(...)`. The ^cJSON # filter drops the CJSON_PUBLIC macro's own definition lines, which have the same # shape and would otherwise contribute `type`, `__declspec` and `__attribute__` as # phantom names. api_names() { grep -oE 'CJSON_PUBLIC\([^)]*\)[[:space:]]+[A-Za-z_][A-Za-z0-9_]*' "$1" 2>/dev/null \ | awk '{print $NF}' | grep -E '^cJSON' | sort -u } api_names "$inc" > "$namef" api_names "$incu" > "$nameuf" # One batched lookup per archive instead of one grep per symbol: -o -F -f reports # every listed name it finds, and the exact-line match in hasline below then reads # that result without forking again. found=$(grep -oFf "$namef" "$flat" 2>/dev/null | sort -u) foundu=$(grep -oFf "$nameuf" "$flatu" 2>/dev/null | sort -u) ndecl=$(grep -c . "$namef"); nfound=$(printf '%s' "$found" | grep -c .) ndeclu=$(grep -c . "$nameuf"); nfoundu=$(printf '%s' "$foundu" | grep -c .) # The floors guard the check itself: a header that lost its declarations, or an # api_names that silently matched nothing, would otherwise pass vacuously with # zero names to look up. if [ "$ndecl" -ge 70 ] && [ "$nfound" -eq "$ndecl" ]; then bst_ok cjson/sym-complete else bst_notok cjson/sym-complete "declared=$ndecl found=$nfound"; fi if [ "$ndeclu" -ge 14 ] && [ "$nfoundu" -eq "$ndeclu" ]; then bst_ok cjson/usym-complete else bst_notok cjson/usym-complete "declared=$ndeclu found=$nfoundu"; fi # A named sample across the layers as well, so a failure names the layer that # broke rather than only reporting a count. Matched whole-line against the batched # result, because cJSON_Parse is a prefix of cJSON_ParseWithOpts and a substring # test would let the longer name satisfy the shorter one. for s in cJSON_Parse cJSON_ParseWithLength cJSON_Print cJSON_PrintUnformatted \ cJSON_Minify cJSON_Delete cJSON_Duplicate cJSON_Compare \ cJSON_CreateObject cJSON_CreateArray cJSON_CreateString cJSON_CreateNumber \ cJSON_AddItemToObject cJSON_GetObjectItemCaseSensitive cJSON_GetArrayItem \ cJSON_GetStringValue cJSON_GetNumberValue cJSON_IsNumber cJSON_IsObject \ cJSON_GetErrorPtr cJSON_InitHooks cJSON_free cJSON_Version; do hasline "cjson/sym-$s" "$s" "$found" done for s in cJSONUtils_GetPointerCaseSensitive cJSONUtils_ApplyPatchesCaseSensitive \ cJSONUtils_GeneratePatchesCaseSensitive cJSONUtils_MergePatchCaseSensitive \ cJSONUtils_GenerateMergePatch cJSONUtils_SortObjectCaseSensitive \ cJSONUtils_AddPatchToArray cJSONUtils_FindPointerFromObjectTo; do hasline "cjson/usym-$s" "$s" "$foundu" done # cJSON_Utils calls into cJSON and links against it -- the .pc's Requires above # says so, and the archive shows it: cJSON entry points appear in the utils object # as undefined references, which is why the two archives must ship together. ua=$(cat "$flatu" 2>/dev/null) has cjson/ulib-references-cjson "cJSON_GetObjectItem" "$ua" # The version string cJSON_Version() returns is built from the same three macros # the header defines and lands in the archive as a literal, so finding it there # pins the compiled code to the header's version rather than only to the .pc. a=$(cat "$flat" 2>/dev/null) has cjson/lib-version-string "1.7.19" "$a" rm -f "$flat" "$flatu" "$namef" "$nameuf" # --- static-only guards ------------------------------------------------------- # # The stock recipe asks cmake for BOTH a shared and a static library, and cJSON's # own default is shared; on this arm that combination does not merely fail to # link, it stops cmake's GENERATE step (a wasm side module is neither ELF nor # XCOFF, so the install-time RPATH edit has no meaning). A stray .so or .la here # would mean the bbappend's static selection was lost. noglob_ok() { # ok iff nothing matches the glob already expanded into "$@" _id=$1; shift if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi } set -- /usr/lib/libcjson.so*; noglob_ok cjson/no-shared "$@" set -- /usr/lib/libcjson_utils.so*; noglob_ok cjson/no-ushared "$@" set -- /usr/lib/libcjson.la; noglob_ok cjson/no-libtool "$@" set -- /usr/lib/libcjson_utils.la; noglob_ok cjson/no-ulibtool "$@"