diff --git a/README.md b/README.md index cd18c7c..302696d 100644 --- a/README.md +++ b/README.md @@ -536,6 +536,9 @@ cJSON is written in ANSI C (or C89, C90). If your compiler or C library doesn't NOTE: ANSI C is not C++ therefore it shouldn't be compiled with a C++ compiler. You can compile it with a C compiler and link it with your C++ code however. Although compiling with a C++ compiler might work, correct behavior is not guaranteed. +Absence of Undefined Behaviors on the test suite guaranteed by [TrustInSoft CI](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON/latest): +[![TrustInSoft CI](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON.svg)](https://ci.trust-in-soft.com/projects/DaveGamble/cJSON) + #### Floating Point Numbers cJSON does not officially support any `double` implementations other than IEEE754 double precision floating point numbers. It might still work with other implementations but bugs with these will be considered invalid. diff --git a/tests/misc_tests.c b/tests/misc_tests.c index 3bf0a1c..81ea4bf 100644 --- a/tests/misc_tests.c +++ b/tests/misc_tests.c @@ -640,7 +640,9 @@ static void cjson_set_valuestring_to_object_should_not_leak_memory(void) ptr1 = item1->valuestring; return_value = cJSON_SetValuestring(cJSON_GetObjectItem(root, "one"), long_valuestring); TEST_ASSERT_NOT_NULL(return_value); +#ifndef __TRUSTINSOFT_ANALYZER__ /* ptr1 was already freed, comparing it with another pointer is Undefined Behavior */ TEST_ASSERT_NOT_EQUAL_MESSAGE(ptr1, return_value, "new valuestring longer than old should reallocate memory") +#endif /* __TRUSTINSOFT_ANALYZER__ */ TEST_ASSERT_EQUAL_STRING(long_valuestring, cJSON_GetObjectItem(root, "one")->valuestring); return_value = cJSON_SetValuestring(cJSON_GetObjectItem(root, "two"), long_valuestring); diff --git a/tests/parse_hex4.c b/tests/parse_hex4.c index 83cbe65..6d0a9c1 100644 --- a/tests/parse_hex4.c +++ b/tests/parse_hex4.c @@ -34,7 +34,12 @@ static void parse_hex4_should_parse_all_combinations(void) unsigned char digits_lower[6]; unsigned char digits_upper[6]; /* test all combinations */ +#if defined(__TRUSTINSOFT_ANALYZER__) + /* Reduce the test's size for TIS CI. */ + for (number = 0; number <= 0xFFF; number++) +#else for (number = 0; number <= 0xFFFF; number++) +#endif { TEST_ASSERT_EQUAL_INT_MESSAGE(4, sprintf((char*)digits_lower, "%.4x", number), "sprintf failed."); TEST_ASSERT_EQUAL_INT_MESSAGE(4, sprintf((char*)digits_upper, "%.4X", number), "sprintf failed."); diff --git a/tis.config b/tis.config new file mode 100644 index 0000000..dd201c9 --- /dev/null +++ b/tis.config @@ -0,0 +1,233 @@ +[ + { + "name": "cjson_add.c", + "files": [ + "tests/cjson_add.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "compare_tests.c", + "files": [ + "tests/compare_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "json_patch_tests.c", + "files": [ + "tests/json_patch_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "minify_tests.c", + "files": [ + "tests/minify_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "misc_tests.c", + "files": [ + "tests/misc_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "misc_utils_tests.c", + "files": [ + "tests/misc_utils_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "old_utils_tests.c", + "files": [ + "tests/old_utils_tests.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_array.c", + "files": [ + "tests/parse_array.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_examples.c", + "files": [ + "tests/parse_examples.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_hex4.c", + "files": [ + "tests/parse_hex4.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_number.c", + "files": [ + "tests/parse_number.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_object.c", + "files": [ + "tests/parse_object.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_string.c", + "files": [ + "tests/parse_string.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_value.c", + "files": [ + "tests/parse_value.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "parse_with_opts.c", + "files": [ + "tests/parse_with_opts.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "print_array.c", + "files": [ + "tests/print_array.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "print_number.c", + "files": [ + "tests/print_number.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "print_object.c", + "files": [ + "tests/print_object.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "print_string.c", + "files": [ + "tests/print_string.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "print_value.c", + "files": [ + "tests/print_value.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "readme_examples.c", + "files": [ + "tests/readme_examples.c" + ], + "include": "trustinsoft/common.config" + }, + { + "name": "afl.c test1", + "val-args": " fuzzing/inputs/test1", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test10", + "val-args": " fuzzing/inputs/test10", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test11", + "val-args": " fuzzing/inputs/test11", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test2", + "val-args": " fuzzing/inputs/test2", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test3", + "val-args": " fuzzing/inputs/test3", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test3.bu", + "val-args": " fuzzing/inputs/test3.bu", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test3.uf", + "val-args": " fuzzing/inputs/test3.uf", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test3.uu", + "val-args": " fuzzing/inputs/test3.uu", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test4", + "val-args": " fuzzing/inputs/test4", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test5", + "val-args": " fuzzing/inputs/test5", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test6", + "val-args": " fuzzing/inputs/test6", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test7", + "val-args": " fuzzing/inputs/test7", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test8", + "val-args": " fuzzing/inputs/test8", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + }, + { + "name": "afl.c test9", + "val-args": " fuzzing/inputs/test9", + "include": "trustinsoft/common.config", + "include": "trustinsoft/fuzz.config" + } +] \ No newline at end of file diff --git a/trustinsoft/common.config b/trustinsoft/common.config new file mode 100644 index 0000000..c764f25 --- /dev/null +++ b/trustinsoft/common.config @@ -0,0 +1,112 @@ +{ + "files": [ + "../cJSON_Utils.c", + "../tests/unity/src/unity.c" + ], + "compilation_cmd": "-DUNITY_EXCLUDE_SETJMP_H", + "val-clone-on-recursive-calls-max-depth": 10000, + "filesystem": { + "files": [ + { + "name": "json-patch-tests/cjson-utils-tests.json", + "from": "../tests/json-patch-tests/cjson-utils-tests.json" + }, + { + "name": "json-patch-tests/package.json", + "from": "../tests/json-patch-tests/package.json" + }, + { + "name": "json-patch-tests/spec_tests.json", + "from": "../tests/json-patch-tests/spec_tests.json" + }, + { + "name": "json-patch-tests/tests.json", + "from": "../tests/json-patch-tests/tests.json" + }, + { + "name": "inputs/test1", + "from": "../tests/inputs/test1" + }, + { + "name": "inputs/test1.expected", + "from": "../tests/inputs/test1.expected" + }, + { + "name": "inputs/test10", + "from": "../tests/inputs/test10" + }, + { + "name": "inputs/test10.expected", + "from": "../tests/inputs/test10.expected" + }, + { + "name": "inputs/test11", + "from": "../tests/inputs/test11" + }, + { + "name": "inputs/test11.expected", + "from": "../tests/inputs/test11.expected" + }, + { + "name": "inputs/test2", + "from": "../tests/inputs/test2" + }, + { + "name": "inputs/test2.expected", + "from": "../tests/inputs/test2.expected" + }, + { + "name": "inputs/test3", + "from": "../tests/inputs/test3" + }, + { + "name": "inputs/test3.expected", + "from": "../tests/inputs/test3.expected" + }, + { + "name": "inputs/test4", + "from": "../tests/inputs/test4" + }, + { + "name": "inputs/test4.expected", + "from": "../tests/inputs/test4.expected" + }, + { + "name": "inputs/test5", + "from": "../tests/inputs/test5" + }, + { + "name": "inputs/test5.expected", + "from": "../tests/inputs/test5.expected" + }, + { + "name": "inputs/test6", + "from": "../tests/inputs/test6" + }, + { + "name": "inputs/test7", + "from": "../tests/inputs/test7" + }, + { + "name": "inputs/test7.expected", + "from": "../tests/inputs/test7.expected" + }, + { + "name": "inputs/test8", + "from": "../tests/inputs/test8" + }, + { + "name": "inputs/test8.expected", + "from": "../tests/inputs/test8.expected" + }, + { + "name": "inputs/test9", + "from": "../tests/inputs/test9" + }, + { + "name": "inputs/test9.expected", + "from": "../tests/inputs/test9.expected" + } + ] + } +} \ No newline at end of file diff --git a/trustinsoft/fuzz.config b/trustinsoft/fuzz.config new file mode 100644 index 0000000..3b052ad --- /dev/null +++ b/trustinsoft/fuzz.config @@ -0,0 +1,66 @@ +{ + "files": [ + "../cJSON.c", + "../fuzzing/afl.c" + ], + "filesystem": { + "files": [ + { + "name": "fuzzing/inputs/test1", + "from": "../fuzzing/inputs/test1" + }, + { + "name": "fuzzing/inputs/test10", + "from": "../fuzzing/inputs/test10" + }, + { + "name": "fuzzing/inputs/test11", + "from": "../fuzzing/inputs/test11" + }, + { + "name": "fuzzing/inputs/test2", + "from": "../fuzzing/inputs/test2" + }, + { + "name": "fuzzing/inputs/test3", + "from": "../fuzzing/inputs/test3" + }, + { + "name": "fuzzing/inputs/test3.bu", + "from": "../fuzzing/inputs/test3.bu" + }, + { + "name": "fuzzing/inputs/test3.uf", + "from": "../fuzzing/inputs/test3.uf" + }, + { + "name": "fuzzing/inputs/test3.uu", + "from": "../fuzzing/inputs/test3.uu" + }, + { + "name": "fuzzing/inputs/test4", + "from": "../fuzzing/inputs/test4" + }, + { + "name": "fuzzing/inputs/test5", + "from": "../fuzzing/inputs/test5" + }, + { + "name": "fuzzing/inputs/test6", + "from": "../fuzzing/inputs/test6" + }, + { + "name": "fuzzing/inputs/test7", + "from": "../fuzzing/inputs/test7" + }, + { + "name": "fuzzing/inputs/test8", + "from": "../fuzzing/inputs/test8" + }, + { + "name": "fuzzing/inputs/test9", + "from": "../fuzzing/inputs/test9" + } + ] + } +} \ No newline at end of file diff --git a/trustinsoft/regenerate.py b/trustinsoft/regenerate.py new file mode 100644 index 0000000..a6372c1 --- /dev/null +++ b/trustinsoft/regenerate.py @@ -0,0 +1,198 @@ +#! /usr/bin/env python3 + +# This script regenerates TrustInSoft CI configuration. + +# Run from the root of the cJSON project: +# $ python3 trustinsoft/regenerate.py + +import re # sub +import json # dumps, load +from os import path # basename, isdir, join +import glob # iglob + +# Outputting JSON. +def string_of_json(obj): + # Output standard pretty-printed JSON (RFC 7159) with 4-space indentation. + s = json.dumps(obj, indent=4) + # Sometimes we need to have multiple "include" fields in the outputted + # JSON, which is unfortunately impossible in the internal python + # representation (OK, it is technically possible, but too cumbersome to + # bother implementing it here), so we can name these fields 'include_', + # 'include__', etc, and they are all converted to 'include' before + # outputting as JSON. + s = re.sub(r'"include_+"', '"include"', s) + return s + +# Make a command line from a dictionary of lists. +def string_of_options(options): + elts = [] + for opt_prefix in options: # e.g. opt_prefix == "-D" + for opt_value in options[opt_prefix]: # e.g. opt_value == "HAVE_OPEN" + elts.append(opt_prefix + opt_value) # e.g. "-DHAVE_OPEN" + return " ".join(elts) + + +# Directories. +test_files_dir = "tests" +fuzz_input_dir = path.join("fuzzing", "inputs") + +# --------------------------------------------------------------------------- # +# ---------------------------------- CHECKS --------------------------------- # +# --------------------------------------------------------------------------- # + +def check_dir(dir): + if path.isdir(dir): + print(" > OK! Directory '%s' exists." % dir) + else: + exit("Directory '%s' not found." % dir) + +# Initial check. +print("1. Check if all necessary directories and files exist...") +check_dir("trustinsoft") +check_dir(test_files_dir) +check_dir(fuzz_input_dir) + +# --------------------------------------------------------------------------- # +# -------------------- GENERATE trustinsoft/common.config ------------------- # +# --------------------------------------------------------------------------- # + +common_config_path = path.join("trustinsoft", "common.config") + +def make_common_config(): + # C files. + c_files = [ + "cJSON_Utils.c", + path.join("tests", "unity", "src", "unity.c"), + ] + # Compilation options. + compilation_cmd = ( + { + "-I": [], + "-D": [ + "UNITY_EXCLUDE_SETJMP_H" + ], + "-U": [], + } + ) + # Filesystem. + json_patch_tests = list( + map(lambda file: + { + "name": path.join("json-patch-tests", path.basename(file)), + "from": path.join("..", file), + }, + sorted(glob.iglob(path.join("tests", "json-patch-tests", "*.json"), + recursive=False))) + ) + tests_and_expected = list( + map(lambda file: + { + "name": path.join("inputs", path.basename(file)), + "from": path.join("..", file), + }, + sorted(glob.iglob(path.join("tests", "inputs", "test*"), + recursive=False))) + ) + # Whole common.config JSON. + config = ( + { + "files": list(map(lambda file: path.join("..", file), c_files)), + "compilation_cmd": string_of_options(compilation_cmd), + "val-clone-on-recursive-calls-max-depth": 10000, + "filesystem": { "files": json_patch_tests + tests_and_expected }, + } + ) + # Done. + return config + +common_config = make_common_config() +with open(common_config_path, "w") as file: + print("2. Generate the '%s' file." % common_config_path) + file.write(string_of_json(common_config)) + +# --------------------------------------------------------------------------- # +# -------------------- GENERATE trustinsoft/fuzz.config --------------------- # +# --------------------------------------------------------------------------- # + +fuzz_config_path = path.join("trustinsoft", "fuzz.config") + +def make_fuzz_config(): + # C files. + c_files = [ + "cJSON.c", + path.join("fuzzing", "afl.c"), + ] + # Filesystem. + fuzzing_files = list( + map(lambda file: + { + "name": path.join(fuzz_input_dir, path.basename(file)), + "from": path.join("..", file), + }, + sorted(glob.iglob(path.join(fuzz_input_dir, "test*"), + recursive=False))) + ) + # Whole fuzz.config JSON. + config = ( + { + "files": list(map(lambda file: path.join("..", file), c_files)), + "filesystem": { "files": fuzzing_files }, + } + ) + # Done. + return config + +fuzz_config = make_fuzz_config() +with open(fuzz_config_path, "w") as file: + print("3. Generate the '%s' file." % fuzz_config_path) + file.write(string_of_json(fuzz_config)) + +# --------------------------------------------------------------------------- # +# -------------------------------- tis.config ------------------------------- # +# --------------------------------------------------------------------------- # + +exclude_tests = [ + "unity_setup.c" +] + +def test_files(): + test_files = sorted( + glob.iglob(path.join(test_files_dir, "*.c"), recursive=False) + ) + for exclude_test in exclude_tests: + test_files.remove(path.join("tests", exclude_test)) + return test_files + +def make_test(test_file): + basename = path.basename(test_file) + return ( + { + "name": basename, + "files": [ test_file ], + "include": common_config_path, + } + ) + +def fuzz_input_files(): + return sorted( + glob.iglob(path.join(fuzz_input_dir, "test*"), recursive=False) + ) + +def make_fuzz_test(fuzz_input_file): + basename = path.basename(fuzz_input_file) + return ( + { + "name": ("afl.c " + basename), + "val-args": " " + path.join(fuzz_input_dir, basename), + "include": common_config_path, + "include_": fuzz_config_path, + } + ) + +tis_config = ( + list(map(make_test, test_files())) + + list(map(make_fuzz_test, fuzz_input_files())) +) +with open("tis.config", "w") as file: + print("4. Generate the 'tis.config' file.") + file.write(string_of_json(tis_config))