diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..d3f2293 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,6 @@ +FROM gcr.io/oss-fuzz-base/base-builder +RUN apt-get update && apt-get install -y make autoconf automake libtool + +COPY . $SRC/json_struct +COPY .clusterfuzzlite/build.sh $SRC/build.sh +WORKDIR $SRC/json_struct \ No newline at end of file diff --git a/.clusterfuzzlite/README.md b/.clusterfuzzlite/README.md new file mode 100644 index 0000000..6461d7a --- /dev/null +++ b/.clusterfuzzlite/README.md @@ -0,0 +1,4 @@ +# ClusterFuzzLite set up + +This folder contains a fuzzing set for [ClusterFuzzLite](https://google.github.io/clusterfuzzlite). + \ No newline at end of file diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100644 index 0000000..6710044 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash -eu +# Copy fuzzer executables to $OUT/ +$CXX $CXXFLAGS -fsanitize=fuzzer $SRC/json_struct/.clusterfuzzlite/reformat_fuzzer.cpp -o $OUT/reformat_fuzzer -I$SRC/json_struct/include diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..7f563eb --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c++ \ No newline at end of file diff --git a/.clusterfuzzlite/reformat_fuzzer.cpp b/.clusterfuzzlite/reformat_fuzzer.cpp new file mode 100644 index 0000000..adbedb3 --- /dev/null +++ b/.clusterfuzzlite/reformat_fuzzer.cpp @@ -0,0 +1,10 @@ +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + std::string pretty; + JS::reformat((const char *)data, size, pretty); + + return 0; +} \ No newline at end of file diff --git a/.github/actions/docker-build-action/action.yml b/.github/actions/docker-build-action/action.yml new file mode 100644 index 0000000..8c49ca3 --- /dev/null +++ b/.github/actions/docker-build-action/action.yml @@ -0,0 +1,54 @@ +name: 'Docker Build and Push' +description: 'Builds and pushes Docker images to GitHub Container Registry' + +inputs: + imageName: + description: 'Name of the image to build and push' + required: true + dockerFile: + description: 'Filepath relative to the repository root' + required: true + user: + description: 'User name used for ghcr' + required: true + password: + description: 'Password for user when logging in to ghcr' + required: true +outputs: + imageTag: + description: 'The tag of the built Docker image' + value: ghcr.io/${{ github.repository_owner }}/${{ inputs.imageName }} +runs: + using: 'composite' + steps: + - name: Set up environment variables + shell: bash + run: echo "FULL_IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/${{ inputs.imageName }}" >> $GITHUB_ENV + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Log in to GitHub Container Registry + shell: bash + run: docker login ghcr.io -u ${{ inputs.user }} -p ${{ inputs.password }} + + - name: Check if Docker image exists + id: check_image + shell: bash + run: | + if docker manifest inspect $FULL_IMAGE_NAME; then + echo "IMAGE_EXISTS=true" >> $GITHUB_ENV + else + echo "IMAGE_EXISTS=false" >> $GITHUB_ENV + fi + + - name: Build Docker image + if: env.IMAGE_EXISTS == 'false' + shell: bash + run: docker build -t $FULL_IMAGE_NAME -f ${{ inputs.dockerFile }} . + + - name: Push Docker image + if: env.IMAGE_EXISTS == 'false' + shell: bash + run: docker push $FULL_IMAGE_NAME + diff --git a/.github/workflows/build_and_test_with_sanitizers.yml b/.github/workflows/build_and_test_with_sanitizers.yml new file mode 100644 index 0000000..b335319 --- /dev/null +++ b/.github/workflows/build_and_test_with_sanitizers.yml @@ -0,0 +1,45 @@ +name: Build and Test with Clang sanitizers + +on: [push, pull_request] + +jobs: + build-and-push-docker-image: + runs-on: ubuntu-latest + outputs: + imageTag: ${{ steps.docker-build.outputs.imageTag }} + steps: + - uses: actions/checkout@v2 + - name: Build and Push Docker Image + id: docker-build + uses: ./.github/actions/docker-build-action + with: + imageName: json_struct_sanitizer_docker:2 + dockerFile: docker/ubuntu22.04/Dockerfile + user: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + test-address-sanitizer: + runs-on: ubuntu-latest + needs: build-and-push-docker-image + container: ${{ needs.build-and-push-docker-image.outputs.imageTag }} + steps: + - uses: actions/checkout@v2 + - name: Run AddressSanitizer + run: | + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -GNinja .. + ninja + ctest . + + test-memory-sanitizer: + runs-on: ubuntu-latest + needs: build-and-push-docker-image + container: ${{ needs.build-and-push-docker-image.outputs.imageTag }} + steps: + - uses: actions/checkout@v2 + - name: Run MemorySanitizer + run: | + mkdir build && cd build + cmake -DCMAKE_CXX_FLAGS="$MSAN_CFLAGS" -DCMAKE_EXE_LINKER_FLAGS="$MSAN_LFLAGS" -DCMAKE_BUILD_TYPE=Debug -GNinja .. + ninja + ctest . diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml new file mode 100644 index 0000000..4e0c244 --- /dev/null +++ b/.github/workflows/cflite_pr.yml @@ -0,0 +1,31 @@ +name: ClusterFuzzLite PR fuzzing +on: + workflow_dispatch: + push: + branches: [ master ] + pull_request: + branches: [ master ] +permissions: read-all +jobs: + PR: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: [address] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + language: c++ + bad-build-check: false + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + fuzz-seconds: 100 + mode: 'code-change' + report-unreproducible-crashes: false + sanitizer: ${{ matrix.sanitizer }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..6f5ab88 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,80 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +env: + JSON_STRUCT_DISABLE_PCH: ON + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '44 12 * * 6' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'c-cpp' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml new file mode 100644 index 0000000..681e5ca --- /dev/null +++ b/.github/workflows/msvc.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# Find more information at: +# https://github.com/microsoft/msvc-code-analysis-action + +name: Microsoft C++ Code Analysis + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '16 3 * * 0' + +env: + # Path to the CMake build directory. + build: '${{ github.workspace }}/build' + config: 'Debug' + +permissions: + contents: read + +jobs: + analyze: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Analyze + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }} -DJSON_STRUCT_OPT_DISABLE_PCH=ON + + - name: Build CMake + run: cmake --build ${{ env.build }} + + - name: Initialize MSVC Code Analysis + uses: microsoft/msvc-code-analysis-action@v0.1.1 + # Provide a unique ID to access the sarif output path + id: run-analysis + with: + cmakeBuildDirectory: ${{ env.build }} + buildConfiguration: ${{ env.config }} + # Ruleset file that will determine what checks will be run + ruleset: NativeRecommendedRules.ruleset + + # Upload SARIF file to GitHub Code Scanning Alerts + - name: Upload SARIF to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.run-analysis.outputs.sarif }} + + # Upload SARIF file as an Artifact to download and view + # - name: Upload SARIF as an Artifact + # uses: actions/upload-artifact@v3 + # with: + # name: sarif-file + # path: ${{ steps.run-analysis.outputs.sarif }} diff --git a/.gitignore b/.gitignore index 2130f4d..43e5820 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ tests/json-tokenizer-partial-test tests/json-tree-test tests/json-tree-printer-test +*CMakeUserPresets.json +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d2f8dd..4a7f8a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ ### Project -cmake_minimum_required(VERSION 3.16) -project(json_struct VERSION "1.0.0" HOMEPAGE_URL "https://github.com/jorgen/json_struct" DESCRIPTION "A library for parsing JSON directly to C++ structs and vice versa." LANGUAGES CXX) +cmake_minimum_required(VERSION 3.15) +project(json_struct VERSION "1.0.3" HOMEPAGE_URL "https://github.com/jorgen/json_struct" DESCRIPTION "A library for parsing JSON directly to C++ structs and vice versa." LANGUAGES CXX) set(CPACK_PACKAGE_VENDOR "Jørgen Lind") set(ADDITIONAL_MODULES_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake") @@ -17,11 +17,18 @@ else() endif() SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) +if (DEFINED ENV{JSON_STRUCT_DISABLE_PCH}) + set(JSON_STRUCT_OPT_DISABLE_PCH_DEFAULT "$ENV{JSON_STRUCT_DISABLE_PCH}") +else() + set(JSON_STRUCT_OPT_DISABLE_PCH_DEFAULT "OFF") +endif() + ### Options -option(JSON_STRUCT_OPT_BUILD_BENCHMARKS "Build json_struct benchmarks" ${IS_TOPLEVEL_PROJECT}) +option(JSON_STRUCT_OPT_BUILD_BENCHMARKS "Build json_struct benchmarks" OFF) option(JSON_STRUCT_OPT_BUILD_EXAMPLES "Build json_struct examples" ${IS_TOPLEVEL_PROJECT}) option(JSON_STRUCT_OPT_BUILD_TESTS "Build and perform json_struct tests" ${IS_TOPLEVEL_PROJECT}) option(JSON_STRUCT_OPT_INSTALL "Generate and install json_struct target" ${IS_TOPLEVEL_PROJECT}) +option(JSON_STRUCT_OPT_DISABLE_PCH "Disable precompiled headers" ${JSON_STRUCT_OPT_DISABLE_PCH_DEFAULT}) ### Compiler function(set_compiler_flags_for_target target) @@ -30,6 +37,8 @@ function(set_compiler_flags_for_target target) if(MSVC_VERSION GREATER_EQUAL 1920) #VS 2019 and above target_compile_options(${target} PRIVATE /W4 /WX) endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wno-array-bounds) else() target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Werror) endif() @@ -62,12 +71,6 @@ if(JSON_STRUCT_OPT_BUILD_BENCHMARKS) add_subdirectory(performance) endif() -### clangformat -add_custom_target(clangformat - COMMAND clang-format -i include/json_struct/json_struct.h - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - SOURCES include/json_struct/json_struct.h) - ### INSTALL include(GNUInstallDirs) include(GenPkgConfig) diff --git a/README.md b/README.md index a6521d3..0acf031 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # **Structurize your JSON** [![Build status](https://ci.appveyor.com/api/projects/status/mduab0w8u12atfbu?svg=true)](https://ci.appveyor.com/project/jorgen36373/json-struct) +[![ClusterFuzzLite PR fuzzing](https://github.com/jorgen/json_struct/actions/workflows/cflite_pr.yml/badge.svg)](https://github.com/jorgen/json_struct/actions/workflows/cflite_pr.yml) json_struct is a single header only library that parses JSON to C++ structs/classes and serializing structs/classes to JSON. @@ -267,3 +268,13 @@ https://github.com/jorgen/json_struct/tree/master/examples and have a look at the more complete unit tests at: https://github.com/jorgen/json_struct/tree/master/tests + + +## SAST Tools + +- [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. + +## DAST Tools +All tests are run with Clang Address Sanitizer and Memory Sanitizers on pull requests. +- [Clang Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) +- [Clang Memory Sanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..a72d1b0 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,64 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMakeDeps, CMake, cmake_layout +from conan.tools.env import VirtualBuildEnv, VirtualRunEnv +from conan.tools.scm import Git, Version +from conan.tools.files import copy + +import os + +class JsonStructLibrary(ConanFile): + name = "json_struct" + + # Metadata + license = "MIT" + version = "1.0.0" + author = "Jørgen Lind " + url = "https://github.com/jorgen/json_struct" + description = "json_struct is a single header only C++ library for parsing JSON directly to C++ structs and vice versa" + + topics = ("serialization", "deserialization", "reflection", "json") + + settings = "os", "compiler", "build_type", "arch" + pacgake_type = "header-library" + implements = ["auto_header_only"] + exports_sources = "include/*", "cmake/*", "CMakeLists.txt", "COPYING", "README.md", "package.xml" + + options = { + "opt_build_benchmarks": [True, False], + "opt_build_examples": [True, False], + "opt_build_tests": [True, False], + "opt_disable_pch": [True, False], + "opt_install": [True, False], + } + + default_options = { + "opt_build_benchmarks": False, + "opt_build_examples": False, + "opt_build_tests": False, + "opt_disable_pch": False, + "opt_install": True, + } + + def generate(self): + toolchain = CMakeToolchain(self) + + toolchain.variables["JSON_STRUCT_OPT_BUILD_BENCHMARKS"] = self.options.opt_build_benchmarks.value + toolchain.variables["JSON_STRUCT_OPT_BUILD_EXAMPLES"] = self.options.opt_build_examples.value + toolchain.variables["JSON_STRUCT_OPT_BUILD_TESTS"] = self.options.opt_build_tests.value + toolchain.variables["JSON_STRUCT_OPT_DISABLE_PCH"] = self.options.opt_disable_pch.value + toolchain.variables["JSON_STRUCT_OPT_INSTALL"] = self.options.opt_install + + toolchain.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + # Invoke cmake --install + cmake = CMake(self) + cmake.install() + + def layout(self): + cmake_layout(self) diff --git a/docker/ubuntu22.04/Dockerfile b/docker/ubuntu22.04/Dockerfile new file mode 100644 index 0000000..56962aa --- /dev/null +++ b/docker/ubuntu22.04/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:jammy +ARG llvmver=17 + +RUN apt update && \ + apt-get install -y software-properties-common gnupg apt-transport-https ca-certificates + +ADD docker/ubuntu22.04/llvm.list /etc/apt/sources.list.d/ +ADD docker/ubuntu22.04/llvm-snapshot.gpg.key.gpg /etc/apt/trusted.gpg.d/ + +RUN apt update && \ + apt-get install -y ca-certificates build-essential clang-$llvmver libc++-$llvmver-dev libc++abi-$llvmver-dev libunwind-$llvmver-dev && \ + ln -s /usr/bin/clang++-$llvmver /usr/bin/clang++ && \ + ln -s /usr/bin/clang-$llvmver /usr/bin/clang + +ENV CXX=clang++ +ENV CC=clang + +RUN apt install -y cmake ninja-build git vim + +RUN git clone --depth=1 https://github.com/llvm/llvm-project && \ + cd llvm-project && \ + mkdir build; \ + cmake -G Ninja -S runtimes -B build -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" -DLLVM_USE_SANITIZER=MemoryWithOrigins -DCMAKE_INSTALL_PREFIX=/opt && \ + ninja -C build cxx cxxabi && \ + ninja -C build install-cxx install-cxxabi + +env MSAN_CFLAGS="-std=c++20 -fsanitize=memory -nostdinc++ -I/opt/include -I/opt/include/c++/v1" +env MSAN_LFLAGS="-fsanitize=memory -stdlib=libc++ -L/opt/lib -lc++abi -Wl,-rpath,/opt/lib" diff --git a/docker/ubuntu22.04/llvm-snapshot.gpg.key.gpg b/docker/ubuntu22.04/llvm-snapshot.gpg.key.gpg new file mode 100644 index 0000000..1af7486 Binary files /dev/null and b/docker/ubuntu22.04/llvm-snapshot.gpg.key.gpg differ diff --git a/docker/ubuntu22.04/llvm.list b/docker/ubuntu22.04/llvm.list new file mode 100644 index 0000000..cf9df5d --- /dev/null +++ b/docker/ubuntu22.04/llvm.list @@ -0,0 +1,3 @@ +deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main +deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main + diff --git a/examples/01_simple_struct.cpp b/examples/01_simple_struct.cpp index bafa32d..53520bd 100644 --- a/examples/01_simple_struct.cpp +++ b/examples/01_simple_struct.cpp @@ -14,7 +14,7 @@ const char json[] = R"json( struct VecMember { std::string key; - double value; + double value = 0.0; JS_OBJ(key, value); }; diff --git a/examples/03_optional_types.cpp b/examples/03_optional_types.cpp index 6f9d130..cbceef3 100644 --- a/examples/03_optional_types.cpp +++ b/examples/03_optional_types.cpp @@ -15,8 +15,8 @@ const char json[] = R"json( struct JsonData { std::string key; - int number; - bool boolean; + int number = 0; + bool boolean = false; //sizeof(JS::Optional) == sizeof(double) however if you want to see //if it has been modified there is the JS::OptionalChecked. It has an "assigned" member //allowing the user to inspect if the type has been assigned diff --git a/examples/13_diff_missing_data.cpp b/examples/13_diff_missing_data.cpp index bd7b70a..113f13d 100644 --- a/examples/13_diff_missing_data.cpp +++ b/examples/13_diff_missing_data.cpp @@ -117,10 +117,13 @@ int main() case JS::DiffType::MissingMembers: { const auto* missingMembers = diffTokens.getMissingMembers(diffToken); - for (const auto& m : *missingMembers) + if (missingMembers) { - std::string missingStr = "\"" + std::string(m.name.data, m.name.size) + "\""; - fprintf(stdout, "Missing member: %s\n", missingStr.c_str()); + for (const auto &m : *missingMembers) + { + std::string missingStr = "\"" + std::string(m.name.data, m.name.size) + "\""; + fprintf(stdout, "Missing member: %s\n", missingStr.c_str()); + } } break; } diff --git a/include/json_struct/json_struct.h b/include/json_struct/json_struct.h index 9e9afc6..4e5b56f 100644 --- a/include/json_struct/json_struct.h +++ b/include/json_struct/json_struct.h @@ -365,6 +365,8 @@ enum class Error : unsigned char ExpectedObjectEnd, ExpectedArrayStart, ExpectedArrayEnd, + UnexpectedArrayEnd, + UnexpectedObjectEnd, IllegalPropertyName, IllegalPropertyType, IllegalDataValue, @@ -405,139 +407,10 @@ class ErrorContext } }; -template -struct CallbackContainer; } // namespace Internal -template -class RefCounter -{ -public: - RefCounter() - : callbackContainer(nullptr) - , index(0) - { - } - - RefCounter(size_t index, Internal::CallbackContainer *callbackContainer) - : callbackContainer(callbackContainer) - , index(index) - { - inc(); - } - RefCounter(const RefCounter &other) - : callbackContainer(other.callbackContainer) - { - inc(); - } - - RefCounter &operator=(const RefCounter &other) - { - dec(); - callbackContainer = other.callbackContainer; - index = other.index; - inc(); - return *this; - } - - ~RefCounter() - { - dec(); - } - -private: - void inc(); - void dec(); - Internal::CallbackContainer *callbackContainer; - size_t index; -}; - -template -class Callback -{ -public: - Callback() - : ref(0) - { - } - - Callback(std::function &callback) - : ref(0) - , callback(callback) - { - } - Callback(const Callback &other) - : ref(other.ref.load()) - , callback(other.callback) - { - } - Callback &operator=(const Callback &other) - { - ref.store(other.ref.load()); - callback = other.callback; - return *this; - } - - void inc() - { - ++ref; - } - void dec() - { - --ref; - } - - std::atomic ref; - std::function callback; -}; - namespace Internal { -template -struct CallbackContainer -{ -public: - const RefCounter addCallback(std::function &callback) - { - for (size_t i = 0; i < vec.size(); i++) - { - if (vec[i].ref.load() == 0) - { - vec[i].callback = callback; - return RefCounter(i, this); - } - } - vec.push_back(Callback(callback)); - return RefCounter(vec.size() - 1, this); - } - - template - void invokeCallbacks(Ts &...args) - { - for (auto &callbackHandler : vec) - { - if (callbackHandler.ref.load()) - { - callbackHandler.callback(args...); - } - } - } - void inc(size_t index) - { - assert(index < vec.size()); - ++vec[index].ref; - } - void dec(size_t index) - { - assert(index < vec.size()); - assert(vec[index].ref.load() != 0); - --vec[index].ref; - } - -private: - std::vector> vec; -}; - struct ScopeCounter { JS::Type type; @@ -559,24 +432,6 @@ struct ScopeCounter }; } // namespace Internal -template -inline void RefCounter::inc() -{ - if (callbackContainer) - callbackContainer->inc(index); -} - -template -inline void RefCounter::dec() -{ - if (callbackContainer) - callbackContainer->dec(index); -} - -class Tokenizer; -typedef RefCounter ReleaseCBRef; -typedef RefCounter NeedMoreDataCBRef; - class Tokenizer { public: @@ -594,8 +449,8 @@ class Tokenizer void resetData(const std::vector *parsedData, size_t index); size_t registeredBuffers() const; - NeedMoreDataCBRef registerNeedMoreDataCallback(std::function callback); - ReleaseCBRef registerReleaseCallback(std::function &callback); + void setNeedMoreDataCallback(std::function callback); + void setReleaseCallback(std::function &callback); Error nextToken(Token &next_token); const char *currentPosition() const; @@ -662,8 +517,8 @@ class Tokenizer std::vector data_list; std::vector scope_counter; std::vector container_stack; - Internal::CallbackContainer release_callbacks; - Internal::CallbackContainer need_more_data_callbacks; + std::function release_callback; + std::function need_more_data_callback; std::vector> copy_buffers; const std::vector *parsed_data_vector; Internal::ErrorContext error_context; @@ -729,15 +584,6 @@ class SerializerOptions std::string m_postfix; }; -#if __cplusplus >= 201403L -template -const static SerializerOptions DEFAULT_OPS = - []() { // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,cert-err58-cpp) - SerializerOptions ops(S); - return ops; - }(); -#endif - class SerializerBuffer { public: @@ -763,8 +609,6 @@ class SerializerBuffer size_t used; }; -class Serializer; -typedef RefCounter BufferRequestCBRef; class Serializer { public: @@ -787,7 +631,7 @@ class Serializer template inline bool write(const Internal::StringLiteral &strLiteral); - const BufferRequestCBRef addRequestBufferCallback(std::function callback); + void setRequestBufferCallback(std::function callback); const SerializerBuffer ¤tBuffer() const; private: @@ -796,7 +640,7 @@ class Serializer bool writeAsString(const DataRef &data); bool write(Type type, const DataRef &data); - Internal::CallbackContainer m_request_buffer_callbacks; + std::function m_request_buffer_callback; SerializerBuffer m_current_buffer; bool m_first; @@ -866,8 +710,11 @@ inline void Tokenizer::addData(const std::vector *parsedData) inline void Tokenizer::resetData(const char *data, size_t size, size_t index) { - for (auto &data_buffer : data_list) - release_callbacks.invokeCallbacks(data_buffer.data); + if (release_callback) + { + for (auto &data_buffer : data_list) + release_callback(data_buffer.data); + } data_list.clear(); parsed_data_vector = nullptr; cursor_index = index; @@ -877,8 +724,11 @@ inline void Tokenizer::resetData(const char *data, size_t size, size_t index) inline void Tokenizer::resetData(const std::vector *parsedData, size_t index) { - for (auto &data_buffer : data_list) - release_callbacks.invokeCallbacks(data_buffer.data); + if (release_callback) + { + for (auto &data_buffer : data_list) + release_callback(data_buffer.data); + } data_list.clear(); parsed_data_vector = parsedData; cursor_index = index; @@ -890,14 +740,14 @@ inline size_t Tokenizer::registeredBuffers() const return data_list.size(); } -inline NeedMoreDataCBRef Tokenizer::registerNeedMoreDataCallback(std::function callback) +inline void Tokenizer::setNeedMoreDataCallback(std::function callback) { - return need_more_data_callbacks.addCallback(callback); + need_more_data_callback = callback; } -inline ReleaseCBRef Tokenizer::registerReleaseCallback(std::function &callback) +inline void Tokenizer::setReleaseCallback(std::function &callback) { - return release_callbacks.addCallback(callback); + release_callback = callback; } inline Error Tokenizer::nextToken(Token &next_token) @@ -959,12 +809,22 @@ inline Error Tokenizer::nextToken(Token &next_token) container_stack.push_back(next_token.value_type); if (next_token.value_type == Type::ArrayEnd) { - assert(container_stack.size() && container_stack.back() == JS::Type::ArrayStart); + if (!container_stack.size() || container_stack.back() != JS::Type::ArrayStart) + { + error = Error::UnexpectedArrayEnd; + updateErrorContext(error); + return error; + } container_stack.pop_back(); } if (next_token.value_type == Type::ObjectEnd) { - assert(container_stack.size() && container_stack.back() == JS::Type::ObjectStart); + if (!container_stack.size() || container_stack.back() != JS::Type::ObjectStart) + { + error = Error::UnexpectedObjectEnd; + updateErrorContext(error); + return error; + } container_stack.pop_back(); } if (scope_counter.size()) @@ -1059,6 +919,8 @@ static const char *error_strings[] = { "ExpectedObjectEnd", "ExpectedArrayStart", "ExpectedArrayEnd", + "UnexpectedArrayEnd", + "UnexpectedObjectEnd", "IllegalPropertyName", "IllegalPropertyType", "IllegalDataValue", @@ -1388,7 +1250,8 @@ inline Error Tokenizer::findTokenEnd(const DataRef &json_data, size_t *chars_ahe inline void Tokenizer::requestMoreData() { - need_more_data_callbacks.invokeCallbacks(*this); + if (need_more_data_callback) + need_more_data_callback(*this); } inline void Tokenizer::releaseFirstDataRef() @@ -1410,7 +1273,8 @@ inline void Tokenizer::releaseFirstDataRef() const char *data_to_release = json_data.data; data_list.erase(data_list.begin()); - release_callbacks.invokeCallbacks(data_to_release); + if (release_callback) + release_callback(data_to_release); } inline Error Tokenizer::populateFromDataRef(DataRef &data, Type &type, const DataRef &json_data) @@ -1754,8 +1618,8 @@ inline Error Tokenizer::updateErrorContext(Error error, const std::string &custo lines.insert(lines.begin(), {0, size_t(cursor_back)}); } } - if (lines.front().start == 0) - lines.front().start = size_t(cursor_back); + if (lines.front().start == 0 && cursor_back > 0) + lines.front().start = size_t(cursor_back); bool add_new_line = false; for (cursor_forward = real_cursor_index; cursor_forward < stop_forward; cursor_forward++) { @@ -1809,7 +1673,7 @@ static inline JS::Error reformat(const char *data, size_t size, std::string &out Serializer serializer; serializer.setOptions(options); size_t last_pos = 0; - auto cbref = serializer.addRequestBufferCallback([&out, &last_pos](Serializer &serializer_p) { + serializer.setRequestBufferCallback([&out, &last_pos](Serializer &serializer_p) { size_t end = out.size(); out.resize(end * 2); serializer_p.setBuffer(&out[0] + end, end); @@ -1819,7 +1683,7 @@ static inline JS::Error reformat(const char *data, size_t size, std::string &out out.resize(4096); serializer.setBuffer(&out[0], out.size()); - while (true) + while (error == Error::NoError) { error = tokenizer.nextToken(token); if (error != Error::NoError) @@ -2127,7 +1991,10 @@ inline bool Serializer::write(const Token &in_token) bool isEnd = token.value_type == Type::ObjectEnd || token.value_type == Type::ArrayEnd; if (isEnd) { - assert(m_option.depth() > 0); + if (m_option.depth() <= 0) + { + return false; + } m_option.setDepth(m_option.depth() - 1); } @@ -2222,9 +2089,9 @@ inline bool Serializer::write(const Token &in_token) return true; } -inline const BufferRequestCBRef Serializer::addRequestBufferCallback(std::function callback) +inline void Serializer::setRequestBufferCallback(std::function callback) { - return m_request_buffer_callbacks.addCallback(callback); + m_request_buffer_callback = callback; } inline const SerializerBuffer &Serializer::currentBuffer() const @@ -2234,7 +2101,8 @@ inline const SerializerBuffer &Serializer::currentBuffer() const inline void Serializer::askForMoreBuffers() { - m_request_buffer_callbacks.invokeCallbacks(*this); + if (m_request_buffer_callback) + m_request_buffer_callback(*this); } inline void Serializer::markCurrentSerializerBufferFull() @@ -3328,7 +3196,7 @@ struct MemberChecker using Super = decltype(Internal::template JsonStructBaseDummy::js_static_meta_super_info()); Error superError = StartSuperRecursion::verifyMembers( assigned_members, track_missing_members, missing_members); - if (memberError != Error::NoError) + if (memberError != Error::NoError)//-V1051 memberError is correct, but we have to allways call supers verifyMembers first return memberError; return superError; } @@ -3497,18 +3365,18 @@ struct SerializerContext { SerializerContext(std::string &json_out_p) : serializer() - , cb_ref(serializer.addRequestBufferCallback([this](Serializer &serializer_p) { - size_t end = this->json_out.size(); - this->json_out.resize(end * 2); - serializer_p.setBuffer(&(this->json_out[0]) + end, end); - this->last_pos = end; - })) , json_out(json_out_p) , last_pos(0) { if (json_out.empty()) json_out.resize(4096); serializer.setBuffer(&json_out[0], json_out.size()); + serializer.setRequestBufferCallback([this](Serializer &serializer_p) { + size_t end = this->json_out.size(); + this->json_out.resize(end * 2); + serializer_p.setBuffer(&(this->json_out[0]) + end, end); + this->last_pos = end; + }); } ~SerializerContext() @@ -3530,7 +3398,6 @@ struct SerializerContext } Serializer serializer; - BufferRequestCBRef cb_ref; std::string &json_out; size_t last_pos; }; @@ -3916,9 +3783,10 @@ struct JsonStructFunctionContainerDummy #define JS_FUNCTION_CONTAINER_EXTERNAL_WITH_SUPER_WITHOUT_MEMBERS(Type, super_list) \ JS_FUNCTION_CONTAINER_EXTERNAL_INTERNAL_IMPL(Type, super_list, JS::makeTuple()) -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 11 +#if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" +#pragma GCC diagnostic ignored "-Wstringop-overflow" #endif namespace Internal @@ -4152,7 +4020,7 @@ struct FunctionCaller }; } // namespace Internal -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 11 +#if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif @@ -4365,13 +4233,13 @@ inline Error CallFunctionContext::callFunctions(T &container) struct DefaultCallFunctionContext : public CallFunctionContext { DefaultCallFunctionContext(std::string &json_out) - : CallFunctionContext(p_context, s_context.serializer) + : CallFunctionContext(p_context, s_context.serializer)//-V1050 The super class only store the reference so we don't mind its not initialized , s_context(json_out) { } DefaultCallFunctionContext(const char *data, size_t size, std::string &json_out) - : CallFunctionContext(p_context, s_context.serializer) + : CallFunctionContext(p_context, s_context.serializer)//-V1050 The super class only store the reference so we don't mind its not initialized , p_context(data, size) , s_context(json_out) { @@ -4943,10 +4811,7 @@ inline void left_shift(uint64_t (&a)[2], int shift) if (shift > int(sizeof(*a)) * 8) { auto shift_0 = (int(sizeof(uint64_t) * 8) - shift); - if (shift_0 > 0) - a[1] = a[0] >> shift_0; - else - a[1] = a[0] << -shift_0; + a[1] = a[0] << -shift_0; a[0] = 0; } @@ -6698,7 +6563,7 @@ inline parse_string_error parseNumber(const char *number, size_t size, parsed_st int desimal_position = -1; bool increase_significand = true; - parsedString.negative = false; + parsedString.negative = 0; parsedString.inf = 0; parsedString.nan = 0; parsedString.significand_digit_count = 0; @@ -6713,7 +6578,7 @@ inline parse_string_error parseNumber(const char *number, size_t size, parsed_st } if (*current == '-') { - parsedString.negative = true; + parsedString.negative = 1; current++; } while (current < number_end) @@ -6760,8 +6625,6 @@ inline parse_string_error parseNumber(const char *number, size_t size, parsed_st { if (desimal_position >= 0) parsedString.exp = desimal_position - parsedString.significand_digit_count; - else - parsedString.exp = 0; return parse_string_error::ok; } current++; @@ -7498,10 +7361,10 @@ struct TypeHandler -struct TypeHandler> +template +struct TypeHandler> { - static inline Error to(std::vector &to_type, ParseContext &context) + static inline Error to(std::vector &to_type, ParseContext &context) { if (context.token.value_type != JS::Type::ArrayStart) return Error::ExpectedArrayStart; @@ -7524,7 +7387,7 @@ struct TypeHandler> return error; } - static inline void from(const std::vector &vec, Token &token, Serializer &serializer) + static inline void from(const std::vector &vec, Token &token, Serializer &serializer) { token.value_type = Type::ArrayStart; token.value = DataRef("["); @@ -7546,11 +7409,11 @@ struct TypeHandler> }; /// \private -template <> -struct TypeHandler> +template +struct TypeHandler> { public: - static inline Error to(std::vector &to_type, ParseContext &context) + static inline Error to(std::vector &to_type, ParseContext &context) { if (context.token.value_type != JS::Type::ArrayStart) return Error::ExpectedArrayStart; @@ -7575,7 +7438,7 @@ struct TypeHandler> return error; } - static inline void from(const std::vector &vec, Token &token, Serializer &serializer) + static inline void from(const std::vector &vec, Token &token, Serializer &serializer) { token.value_type = Type::ArrayStart; token.value = DataRef("["); @@ -7614,20 +7477,20 @@ struct TypeHandler }; /// \private -template -struct TypeHandler> +template +struct TypeHandler> { public: - static inline Error to(SilentVector &to_type, ParseContext &context) + static inline Error to(SilentVector &to_type, ParseContext &context) { - return TypeHandler>::to(to_type.data, context); + return TypeHandler>::to(to_type.data, context); } - static inline void from(const SilentVector &vec, Token &token, Serializer &serializer) + static inline void from(const SilentVector &vec, Token &token, Serializer &serializer) { if (vec.data.size()) { - TypeHandler>::from(vec.data, token, serializer); + TypeHandler>::from(vec.data, token, serializer); } } }; @@ -7652,11 +7515,11 @@ struct TypeHandler> }; /// \private -template <> -struct TypeHandler> +template +struct TypeHandler> { public: - static inline Error to(std::vector &to_type, ParseContext &context) + static inline Error to(std::vector &to_type, ParseContext &context) { if (context.token.value_type != JS::Type::ArrayStart && context.token.value_type != JS::Type::ObjectStart) { @@ -7665,15 +7528,10 @@ struct TypeHandler> } to_type.clear(); to_type.push_back(context.token); - bool buffer_change = false; - auto ref = context.tokenizer.registerNeedMoreDataCallback([&buffer_change](JS::Tokenizer &tokenizer) { - JS_UNUSED(tokenizer); - buffer_change = true; - }); size_t level = 1; Error error = Error::NoError; - while (error == JS::Error::NoError && level && buffer_change == false) + while (error == JS::Error::NoError && level) { error = context.nextToken(); to_type.push_back(context.token); @@ -7682,13 +7540,11 @@ struct TypeHandler> else if (context.token.value_type == Type::ArrayEnd || context.token.value_type == Type::ObjectEnd) level--; } - if (buffer_change) - return Error::NonContigiousMemory; return error; } - static inline void from(const std::vector &from_type, Token &token, Serializer &serializer) + static inline void from(const std::vector &from_type, Token &token, Serializer &serializer) { for (auto &t : from_type) { @@ -7722,17 +7578,11 @@ struct TypeHandler if (context.token.value_type != JS::Type::ArrayStart) return Error::ExpectedArrayStart; - bool buffer_change = false; - auto ref = context.tokenizer.registerNeedMoreDataCallback([&buffer_change](JS::Tokenizer &tokenizer) { - JS_UNUSED(tokenizer); - buffer_change = true; - }); - to_type.ref.data = context.token.value.data; size_t level = 1; Error error = Error::NoError; - while (error == JS::Error::NoError && level && buffer_change == false) + while (error == JS::Error::NoError && level) { error = context.nextToken(); if (context.token.value_type == Type::ArrayStart) @@ -7740,8 +7590,6 @@ struct TypeHandler else if (context.token.value_type == Type::ArrayEnd) level--; } - if (buffer_change) - return Error::NonContigiousMemory; to_type.ref.size = size_t(context.token.value.data + context.token.value.size - to_type.ref.data); @@ -7811,16 +7659,10 @@ struct TypeHandler if (context.token.value_type != JS::Type::ObjectStart) return Error::ExpectedObjectStart; - bool buffer_change = false; - auto ref = context.tokenizer.registerNeedMoreDataCallback([&buffer_change](JS::Tokenizer &tokenizer) { - JS_UNUSED(tokenizer); - buffer_change = true; - }); - to_type.ref.data = context.token.value.data; size_t level = 1; Error error = Error::NoError; - while (error == JS::Error::NoError && level && buffer_change == false) + while (error == JS::Error::NoError && level) { error = context.nextToken(); if (context.token.value_type == Type::ObjectStart) @@ -7828,8 +7670,6 @@ struct TypeHandler else if (context.token.value_type == Type::ObjectEnd) level--; } - if (buffer_change) - return Error::NonContigiousMemory; to_type.ref.size = size_t(context.token.value.data + context.token.value.size - to_type.ref.data); return error; @@ -7911,16 +7751,10 @@ struct TypeHandler return Error::ExpectedObjectStart; } - bool buffer_change = false; - auto ref = context.tokenizer.registerNeedMoreDataCallback([&buffer_change](JS::Tokenizer &tokenizer) { - JS_UNUSED(tokenizer); - buffer_change = true; - }); - to_type.ref.data = context.token.value.data; size_t level = 1; Error error = Error::NoError; - while (error == JS::Error::NoError && level && buffer_change == false) + while (error == JS::Error::NoError && level) { error = context.nextToken(); if (context.token.value_type == openType) @@ -7928,8 +7762,6 @@ struct TypeHandler else if (context.token.value_type == closeType) level--; } - if (buffer_change) - return Error::NonContigiousMemory; to_type.ref.size = size_t(context.token.value.data + context.token.value.size - to_type.ref.data); return error; @@ -8256,7 +8088,9 @@ struct TypeHandlerMap return error; while (context.token.value_type != Type::ObjectEnd) { - Key key(context.token.name.data, context.token.name.size); + std::string str; + Internal::handle_json_escapes_in(context.token.name, str); + Key key(str.data(), str.size()); Value v; error = TypeHandler::to(v, context); to_type[std::move(key)] = std::move(v); @@ -8594,17 +8428,32 @@ struct TypeHandler return error; } - static inline void from(const Map &from_type, Token &, Serializer &serializer) + static inline void from(const Map &from_type, Token &token, Serializer &serializer) { - for (auto &token : from_type.tokens.data) + if (from_type.tokens.data.empty()) { + token.value_type = Type::ObjectStart; + token.value = DataRef("{"); serializer.write(token); + token.name = DataRef(""); + token.value_type = Type::ObjectEnd; + token.value = DataRef("}"); + serializer.write(token); + return; + } + + Token first_token = from_type.tokens.data.front(); + first_token.name = token.name; + serializer.write(first_token); + for (int i = 1; i < int(from_type.tokens.data.size()); i++) + { + serializer.write(from_type.tokens.data[i]); } } }; template -struct ArrayVariableContent +struct ArrayVariableContent//-V730 { T data[COUNT]; size_t size = 0; diff --git a/include/json_struct/json_struct_diff.h b/include/json_struct/json_struct_diff.h index ec03642..f580e16 100644 --- a/include/json_struct/json_struct_diff.h +++ b/include/json_struct/json_struct_diff.h @@ -98,6 +98,7 @@ namespace Internal struct DiffTokens { DiffTokens() + : error(DiffError::NoError) {} explicit DiffTokens(const char* json, size_t size) @@ -462,7 +463,7 @@ namespace Internal } else { - if (baseValue == diffValue) + if (baseValue == diffValue)//-V550 diff.set(diffPos, DiffType::NoDiff); else diff.set(diffPos, DiffType::ValueDiff); @@ -739,9 +740,9 @@ namespace Internal } else if (base.tokens.data.size() && diff.tokens.data.empty()) { - if (diff.tokens.data[0].value_type == Type::ObjectStart) + if (base.tokens.data[0].value_type == Type::ObjectStart) diff.addMissingMembers(0, base, 0); - else if (diff.tokens.data[0].value_type == Type::ArrayStart) + else if (base.tokens.data[0].value_type == Type::ArrayStart) diff.addMissingArrayItems(0, base, 0); else setStateForEntireToken(diff, 0, DiffType::ErroneousRootItem); diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..889a293 --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.15) + +project(JsonStructTester + DESCRIPTION "Tester package for json_struct" + LANGUAGES C CXX) + +find_package(json_struct REQUIRED) + +add_executable(${PROJECT_NAME} main.cpp) + +target_link_libraries(${PROJECT_NAME} json_struct::json_struct) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..f0d4e6b --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,28 @@ +import os + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + + +class JsonStructTest(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def layout(self): + cmake_layout(self) + + def test(self): + if can_run(self): + self.output.info("Checking compiled tester...") + cmd = os.path.join(self.cpp.build.bindir, "JsonStructTester") + self.run(cmd, env="conanrun") + diff --git a/test_package/main.cpp b/test_package/main.cpp new file mode 100644 index 0000000..a61797c --- /dev/null +++ b/test_package/main.cpp @@ -0,0 +1,21 @@ +#include "json_struct/json_struct.h" +#include + +struct MyTestStruct +{ + std::string name; + unsigned age; + JS_OBJ(name, age); +}; + +int main() +{ + MyTestStruct person; + person.name="Jonh"; + person.age=23; + + std::string person_json = JS::serializeStruct(person); + std::cout << person_json << std::endl; + + return 0; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fa2c330..156cee7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,8 +59,8 @@ set(unit_test_sources add_executable(unit-tests ${unit_test_sources}) set_compiler_flags_for_target(unit-tests) target_link_libraries(unit-tests PRIVATE catch_main external_json::rc) -if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") -target_precompile_headers(unit-tests PRIVATE ../include/json_struct/json_struct.h catch2/catch.hpp) +if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0" AND NOT JSON_STRUCT_OPT_DISABLE_PCH) + target_precompile_headers(unit-tests PRIVATE ../include/json_struct/json_struct.h catch2/catch.hpp) endif() add_test(NAME unit-tests COMMAND unit-tests) @@ -93,8 +93,3 @@ endif() #add_executable(unit-tests-experimental json-struct-array-varlength.cpp) #target_link_libraries(unit-tests-experimental PRIVATE catch_main) -add_custom_target(clangformat_tests - COMMAND clang-format -i ${unit_test_sources} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests - SOURCES ${unit_test_sources} - ) diff --git a/tests/CMakeRC.cmake b/tests/CMakeRC.cmake index 30d3694..db2d4cc 100644 --- a/tests/CMakeRC.cmake +++ b/tests/CMakeRC.cmake @@ -34,7 +34,6 @@ endif() set(_version 2.0.0) -cmake_minimum_required(VERSION 3.3) include(CMakeParseArguments) if(COMMAND cmrc_add_resource_library) diff --git a/tests/json-copy-test.cpp b/tests/json-copy-test.cpp index a9dccba..e2bc02e 100644 --- a/tests/json-copy-test.cpp +++ b/tests/json-copy-test.cpp @@ -116,7 +116,7 @@ TEST_CASE("copy_test_js_partial_2", "[tokenizer]") offset += 2; } }; - auto ref = tokenizer.registerNeedMoreDataCallback(func); + tokenizer.setNeedMoreDataCallback(func); js_validate_json(tokenizer); } @@ -137,7 +137,7 @@ TEST_CASE("copy_test_js_partial_3", "[tokenizer]") offset += 1; } }; - auto ref = tokenizer.registerNeedMoreDataCallback(func); + tokenizer.setNeedMoreDataCallback(func); js_validate_json(tokenizer); } diff --git a/tests/json-mias-mat.cpp b/tests/json-mias-mat.cpp index e0bd130..413eace 100644 --- a/tests/json-mias-mat.cpp +++ b/tests/json-mias-mat.cpp @@ -69,7 +69,7 @@ struct RecipeNameIdItem } std::string recipe_name; - int recipe_id; + int recipe_id = 0; JS_OBJECT(JS_MEMBER(recipe_name), JS_MEMBER(recipe_id)); }; diff --git a/tests/json-nullable-test.cpp b/tests/json-nullable-test.cpp index db31d89..9f66544 100644 --- a/tests/json-nullable-test.cpp +++ b/tests/json-nullable-test.cpp @@ -13,7 +13,7 @@ struct SmallStructWithoutNullable struct SmallStruct { - int a; + int a = 0; JS::Nullable b = 2.2f; JS_OBJECT(JS_MEMBER(a), JS_MEMBER(b)); @@ -21,7 +21,7 @@ struct SmallStruct struct SmallStructNullableChecked { - int a; + int a = 0; JS::NullableChecked b = 2.2f; JS_OBJECT(JS_MEMBER(a), JS_MEMBER(b)); diff --git a/tests/json-optional.cpp b/tests/json-optional.cpp index d157e84..9183a6b 100644 --- a/tests/json-optional.cpp +++ b/tests/json-optional.cpp @@ -8,7 +8,7 @@ namespace { struct SmallStructWithoutOptional { - int a; + int a = 0; float b = 2.2f; std::string d; @@ -18,7 +18,7 @@ struct SmallStructWithoutOptional #ifdef JS_STD_OPTIONAL struct SmallStructStd { - int a; + int a = 0; std::optional b = 2.2f; std::optional c; std::optional d; diff --git a/tests/json-struct-polymorphic-map.cpp b/tests/json-struct-polymorphic-map.cpp index 9b637bb..c4b9899 100644 --- a/tests/json-struct-polymorphic-map.cpp +++ b/tests/json-struct-polymorphic-map.cpp @@ -38,14 +38,14 @@ static const char json[] = R"json( struct ComplexFields_t { - int Hello; - int World; + int Hello = 0; + int World = 0; JS_OBJ(Hello, World); }; struct SubObject_t { - bool SimpleMember; + bool SimpleMember = false; std::vector MoreValues; JS_OBJ(SimpleMember, MoreValues); }; @@ -67,10 +67,10 @@ struct Root_t : ComplexFields2(true) { } - int Field1; - bool Field2; + int Field1 = 0; + bool Field2 = false; std::string Field3; - int Field4; + int Field4 = 0; ComplexFields_t ComplexFields; ComplexFields2_t ComplexFields2; JS_OBJ(Field1, Field2, Field3, Field4, ComplexFields, ComplexFields2); @@ -226,7 +226,39 @@ TEST_CASE("to_clean_map", "json_struct, map") REQUIRE(pc.error == JS::Error::NoError); REQUIRE(map.castTo("Hello", pc) == "Foo"); REQUIRE(pc.error == JS::Error::NoError); - REQUIRE(map.castTo("World", pc) == "Bar"); + REQUIRE(map.castTo("World", pc) == "Bar"); +} + +struct ParsedJsonMap { + std::string regularKey; + struct { + std::string innerKey1; + int innerKey2; + JS_OBJ(innerKey1, innerKey2); + } mapKey; + JS_OBJ(regularKey, mapKey); +}; + + +TEST_CASE("nested_map", "json_struct, map") +{ + JS::Map nestedMap; + JS::ParseContext parseContext; + nestedMap.setValue("innerKey1", parseContext, std::string("value1")); + nestedMap.setValue("innerKey2", parseContext, 42); + + JS::Map outerMap; + outerMap.setValue("regularKey", parseContext, std::string("simpleValue")); + outerMap.setValue("mapKey", parseContext, nestedMap); + + ParsedJsonMap parsed_json; + + std::string output_json = JS::serializeStruct(outerMap); + JS::ParseContext parseContext2(output_json.data(), output_json.size(), parsed_json); + REQUIRE(parseContext2.error == JS::Error::NoError); + REQUIRE(parsed_json.regularKey == "simpleValue"); + REQUIRE(parsed_json.mapKey.innerKey1 == "value1"); + REQUIRE(parsed_json.mapKey.innerKey2 == 42); } //TEST_CASE("fail_to_compile", "json_struct, map") diff --git a/tests/json-struct-stdint.cpp b/tests/json-struct-stdint.cpp index f7c12ea..352a353 100644 --- a/tests/json-struct-stdint.cpp +++ b/tests/json-struct-stdint.cpp @@ -57,7 +57,7 @@ TEST_CASE("js_std_int", "json_struct") context.allow_missing_members = false; context.allow_unasigned_required_members = false; - stdinttypes to_struct; + stdinttypes to_struct = {}; auto error = context.parseTo(to_struct); REQUIRE(error == JS::Error::NoError); REQUIRE(to_struct.int8 == 4); @@ -66,7 +66,7 @@ TEST_CASE("js_std_int", "json_struct") TEST_CASE("large_number_roundtrip", "json_struct") { - stdinttypes to_serialize; + stdinttypes to_serialize = {}; to_serialize.uint64 = 0; to_serialize.uint64 = ~to_serialize.uint64; std::string serialized = JS::serializeStruct(to_serialize); diff --git a/tests/json-struct-test-new.cpp b/tests/json-struct-test-new.cpp index 031a292..9ac5b76 100644 --- a/tests/json-struct-test-new.cpp +++ b/tests/json-struct-test-new.cpp @@ -58,7 +58,7 @@ static const char json_data1[] = "{\n" struct SubStruct { std::string SubString; - int SubNumber; + int SubNumber = 0; std::vector Array; JS::OptionalChecked optional_float; JS::OptionalChecked optional_double; @@ -90,8 +90,8 @@ struct SubStruct3 : public SubStruct2 struct JsonData1 { std::string StringNode; - double NumberNode; - bool BooleanTrue; + double NumberNode = 0.0; + bool BooleanTrue = false; /*! *very special comment for BooleanFalse * @@ -100,12 +100,12 @@ struct JsonData1 * json *} **/ - bool BooleanFalse; + bool BooleanFalse = false; JS::Optional OptionalInt; /// Test structur comment SubStruct TestStruct; JS::Optional> OptionalButWithData; - float unassigned_value; + float unassigned_value = 32.f; std::unique_ptr subStruct2; int Field3 = 243; @@ -253,7 +253,7 @@ static const char error_in_sub[] = R"json( struct ErrorInSubChild { - int ffirst; + int ffirst = 0; JS_OBJ(ffirst); }; @@ -261,7 +261,7 @@ struct ErrorInSub { ErrorInSubChild first; std::string second; - int third; + int third = 0; JS::Optional not_assigned = 999; JS_OBJ(first, second, third); }; @@ -843,7 +843,7 @@ TEST_CASE("short_check_json_meta_outside", "json_struct") auto error = context.parseTo(data); REQUIRE(error == JS::Error::NoError); REQUIRE(data.data == "this is some text"); - REQUIRE(data.a == 44.5); + REQUIRE(data.a == 44.5);//-V550 } struct FloatingPointAtTheEnd diff --git a/tests/json-struct-test.cpp b/tests/json-struct-test.cpp index e5a10cd..c637fd5 100644 --- a/tests/json-struct-test.cpp +++ b/tests/json-struct-test.cpp @@ -58,7 +58,7 @@ static const char json_data1[] = "{\n" struct SubStruct { std::string SubString; - int SubNumber; + int SubNumber = 0; std::vector Array; JS::OptionalChecked optional_float; JS::OptionalChecked optional_double; @@ -92,8 +92,8 @@ struct SubStruct3 : public SubStruct2 struct JsonData1 { std::string StringNode; - double NumberNode; - bool BooleanTrue; + double NumberNode = 0.0; + bool BooleanTrue = false; /*! *very special comment for BooleanFalse * @@ -102,12 +102,12 @@ struct JsonData1 * json *} **/ - bool BooleanFalse; + bool BooleanFalse = false; JS::Optional OptionalInt; /// Test structur comment SubStruct TestStruct; JS::Optional> OptionalButWithData; - float unassigned_value; + float unassigned_value = 32.f; std::unique_ptr subStruct2; int Field3 = 243; @@ -254,7 +254,7 @@ static const char error_in_sub[] = R"json({ struct ErrorInSubChild { - int ffirst; + int ffirst = 0; JS_OBJECT(JS_MEMBER(ffirst)); }; @@ -262,7 +262,7 @@ struct ErrorInSub { ErrorInSubChild first; std::string second; - int third; + int third = 0; JS::Optional not_assigned = 999; JS_OBJECT(JS_MEMBER(first), JS_MEMBER(second), JS_MEMBER(third)); }; diff --git a/tests/json-tokenizer-partial-test.cpp b/tests/json-tokenizer-partial-test.cpp index d608c07..66a8c8a 100644 --- a/tests/json-tokenizer-partial-test.cpp +++ b/tests/json-tokenizer-partial-test.cpp @@ -326,27 +326,4 @@ TEST_CASE("check_json_partial_8", "[tokenizer]") REQUIRE(error == JS::Error::NeedMoreData); } -TEST_CASE("check_remove_callback", "[tokenizer]") -{ - JS::Error error = JS::Error::NoError; - JS::Tokenizer tokenizer; - tokenizer.allowAsciiType(true); - tokenizer.allowNewLineAsTokenDelimiter(true); - tokenizer.addData(json_data_partial_8_1, sizeof(json_data_partial_8_1)); - tokenizer.addData(json_data_partial_8_2, sizeof(json_data_partial_8_2)); - JS::Token token; - bool has_been_called = false; - { - auto ref = tokenizer.registerNeedMoreDataCallback([&has_been_called](const JS::Tokenizer &) { - has_been_called = true; - }); - error = tokenizer.nextToken(token); - } - - while (error == JS::Error::NoError && token.value_type != JS::Type::ObjectEnd) - error = tokenizer.nextToken(token); - - REQUIRE(error == JS::Error::NoError); - REQUIRE(has_been_called == false); -} } // namespace json_tokenizer_partial_test diff --git a/tests/json-unordered-map.cpp b/tests/json-unordered-map.cpp index 423f573..a0e4be3 100644 --- a/tests/json-unordered-map.cpp +++ b/tests/json-unordered-map.cpp @@ -43,7 +43,7 @@ TEST_CASE("unordered_map_complex_value", "json_struct") JS::ParseContext parseContext(json); REQUIRE(parseContext.parseTo(dataStruct) == JS::Error::NoError); - REQUIRE(dataStruct.unordered_map["bar"].front() == 2.0); + REQUIRE(dataStruct.unordered_map["bar"].front() == 2.0);//-V550 REQUIRE(dataStruct.map["sail"] == "boat"); REQUIRE(dataStruct.unordered_set.find(7) != dataStruct.unordered_set.end()); REQUIRE(dataStruct.set.find(1.6f) != dataStruct.set.end()); diff --git a/tests/zero-value-test.cpp b/tests/zero-value-test.cpp index 1d582d0..906e7e4 100644 --- a/tests/zero-value-test.cpp +++ b/tests/zero-value-test.cpp @@ -81,10 +81,10 @@ void test_zero_value_parse(const char *const json) pc.parseTo(zero); REQUIRE(pc.error == JS::Error::NoError); - REQUIRE(zero.f_pos_zero == f_pos_zero); - REQUIRE(zero.f_neg_zero == f_neg_zero); - REQUIRE(zero.d_pos_zero == d_pos_zero); - REQUIRE(zero.d_neg_zero == d_neg_zero); + REQUIRE(zero.f_pos_zero == f_pos_zero);//-V550 + REQUIRE(zero.f_neg_zero == f_neg_zero);//-V550 + REQUIRE(zero.d_pos_zero == d_pos_zero);//-V550 + REQUIRE(zero.d_neg_zero == d_neg_zero);//-V550 REQUIRE(memcmp(&f_pos_zero, &zero.f_pos_zero, sizeof(float)) == 0); REQUIRE(memcmp(&f_neg_zero, &zero.f_neg_zero, sizeof(float)) == 0);