From 1c8d3188f55a9b7773e1188d080088c210786bc2 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 09:24:36 -0400 Subject: [PATCH 01/32] Add Jenkinsfile and security policy. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++ SECURITY.md | 1 + 2 files changed, 289 insertions(+) create mode 100644 SECURITY.md diff --git a/Jenkinsfile b/Jenkinsfile index e69de29..b602742 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -0,0 +1,288 @@ +def python_versions +def python_executables_and_wheels_map + +pipeline { + agent { + node { + label 'zOS_pyRACF_RACFu' + } + } + + parameters { + // Note: Each Python version listed must be installed on the + // build agent and must be added to '$PATH' and '$LIBPATH'. + string ( + name: "pythonVersions", + defaultValue: "", + description: ( + "(Required Always) Comma separated list of Python versions to build " + + "wheels for (i.e., Use '12,13' for Python 3.12 and Python 3.13)." + ) + ) + booleanParam( + name: "createRelease", + defaultValue: false, + description: "Toggle whether or not to create a release from this revision." + ) + string( + name: "releaseTag", + defaultValue: "", + description: ( + "(Required When Creating Releases) This will be " + + "the git tag and version number of the release." + ) + ) + string( + name: "gitHubMilestoneLink", + defaultValue: "", + description: ( + "(Required When Creating Releases) This is the GitHub " + + "Milestone URL that coresponds to the release." + ) + ) + booleanParam( + name: "preRelease", + defaultValue: true, + description: "Toggle whether or not this is a pre-release." + ) + } + + options { + ansiColor('css') + } + + stages { + stage('Parameter Validation') { + steps { + script { + if (params.pythonVersions == "") { + error("'pythonVersions' is required parameter.") + } + if (params.createRelease) { + if (params.releaseTag == "") { + error("'releaseTag' is a required parameter when creating a release.") + } + if (params.gitHubMilestoneLink == "") { + error("'gitHubMilestoneLink' is a required parameter when creating a release.") + } + } + } + } + } + stage('Prepare') { + steps { + clean_python_environment() + } + } + stage('Cppcheck') { + steps { + echo "Running cppcheck ..." + sh "make check" + } + } + stage('Unit Test') { + steps { + echo "Running unit tests ..." + sh "make test" + } + } + stage('Create Python Distribution Metadata') { + steps { + script { + python_versions = params.pythonVersions.split(",") + python_executables_and_wheels_map = ( + create_python_executables_and_wheels_map(python_versions) + ) + } + } + } + stage('Publish') { + when { + expression { params.createRelease == true } + } + steps { + publish( + python_executables_and_wheels_map, + params.releaseTag, + env.BRANCH_NAME, + params.gitHubMilestoneLink, + params.preRelease + ) + } + } + } + post { + always { + echo "Cleaning up workspace ..." + cleanWs() + clean_python_environment() + } + } +} + +def create_python_executables_and_wheels_map(python_versions) { + def os = sh( + returnStdout: true, + script: "uname" + ).trim().replace("/", "").toLowerCase() + def zos_release = sh( + returnStdout: true, + script: "uname -r" + ).trim().replace(".", "_") + def processor = sh( + returnStdout: true, + script: "uname -m" + ).trim() + def racfu_version = sh( + returnStdout: true, + script: "cat pyproject.toml | grep version | cut -d'=' -f2 | cut -d'\"' -f2" + ).trim() + + python_executables_and_wheels_map = [:] + + for (version in python_versions) { + python_executables_and_wheels_map["python3.${version}"] = [ + "wheelDefault": ( + "racfu-${racfu_version}-cp3${version}-cp3${version}-${os}_${zos_release}_${processor}.whl" + ), + "wheelPublish": "racfu-${racfu_version}-cp3${version}-none-any.whl", + "tarPublish": "racfu-${racfu_version}.tar.gz" + ] + } + + return python_executables_and_wheels_map +} + +def clean_python_environment() { + echo "Cleaning Python environment ..." + + sh """ + rm -rf ~/.cache + rm -rf ~/.local + """ +} + +def publish( + python_executables_and_wheels_map, + release, + git_branch, + milestone, + pre_release +) { + if (pre_release == true) { + pre_release = "true" + } + else { + pre_release = "false" + } + withCredentials( + [ + string( + credentialsId: 'pyracf-racfu-github-access-token', + variable: 'github_access_token' + ) + ] + ) { + + // Creating GitHub releases: + // https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release + // Uploading release assets: + // https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset + + // Use single quotes for credentials since it is the most secure + // method for interpolating secrets according to the Jenkins docs: + // https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#string-interpolation + + echo "Creating '${release}' GitHub release ..." + + def description = build_description(python_executables_and_wheels_map, release, milestone) + + def release_id = sh( + returnStdout: true, + script: ( + 'curl -f -v -L ' + + '-X POST ' + + '-H "Accept: application/vnd.github+json" ' + + '-H "Authorization: Bearer ${github_access_token}" ' + + '-H "X-GitHub-Api-Version: 2022-11-28" ' + + "https://api.github.com/repos/ambitus/racfu/releases " + + "-d '{" + + " \"tag_name\": \"${release}\"," + + " \"target_commitish\": \"${git_branch}\"," + + " \"name\": \"${release}\"," + + " \"body\": \"${description}\"," + + " \"draft\": false," + + " \"prerelease\": ${pre_release}," + + " \"generate_release_notes\":false" + + "}' | grep '\"id\": ' | head -n1 | cut -d':' -f2 | cut -d',' -f1" + ) + ).trim() + + def tar_published = false + + for (python in python_executables_and_wheels_map.keySet()) { + def wheel_default = python_executables_and_wheels_map[python]["wheelDefault"] + def wheel_publish = python_executables_and_wheels_map[python]["wheelPublish"] + def tar_publish = python_executables_and_wheels_map[python]["tarPublish"] + + echo "Cleaning repo and building '${wheel_default}' ..." + + sh """ + git clean -fdx + ${python} -m pip install build>=1.2.2 + ${python} -m build + mv dist/${wheel_default} dist/${wheel_publish} + """ + + echo "Uploading '${wheel_default}' as '${wheel_publish}' to '${release}' GitHub release ..." + + upload_asset(release_id, wheel_publish) + if (tar_published == false) { + upload_asset(release_id, tar_publish) + tar_published = true + } + else { + sh "rm dist/${tar_publish}" + } + } + } +} + +def upload_asset(release_id, release_asset) { + sh( + 'curl -f -v -L ' + + '-X POST ' + + '-H "Accept: application/vnd.github+json" ' + + '-H "Authorization: Bearer ${github_access_token}" ' + + '-H "X-GitHub-Api-Version: 2022-11-28" ' + + '-H "Content-Type: application/octet-stream" ' + + "\"https://uploads.github.com/repos/ambitus/racfu/releases/${release_id}/assets?name=${release_asset}\" " + + "--data-binary \"@dist/${release_asset}\"" + ) +} + +def build_description(python_executables_and_wheels_map, release, milestone) { + def description = "Release Milestone: ${milestone}\\n \\n \\n" + + for (python in python_executables_and_wheels_map.keySet()) { + def wheel = python_executables_and_wheels_map[python]["wheelPublish"] + def python_executable = python + def python_label = python.replace("python", "Python ") + description += ( + "Install From **${python_label} Wheel Distribution** *(pre-built)*:\\n" + + "```\\ncurl -O -L https://github.com/ambitus/racfu/releases/download/${release}/${wheel} " + + "&& ${python_executable} -m pip install ${wheel}\\n```\\n" + ) + } + + def python = python_executables_and_wheels_map.keySet()[-1] + def tar = python_executables_and_wheels_map[python]["tarPublish"] + description += ( + "Install From **Source Distribution** *(build on install)*:\\n" + + "> :warning: _Requires z/OS Open XL C/C++ 2.1 compiler._\\n" + + "```\\ncurl -O -L https://github.com/ambitus/racfu/releases/download/${release}/${tar} " + + "&& python3 -m pip install ${tar}\\n```\\n" + ) + + return description +} diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3856092 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +To report a security vulenaribily, use the [Report a vulnerability](https://github.com/ambitus/racfu/security/advisories/new) function in GitHub. \ No newline at end of file From f920f7bf8b179a270b3e4ef82b04850b9c72b9b9 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 10:16:24 -0400 Subject: [PATCH 02/32] Fix publish stage. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b602742..d3a375b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -177,9 +177,10 @@ def publish( } withCredentials( [ - string( + usernamePassword( credentialsId: 'pyracf-racfu-github-access-token', - variable: 'github_access_token' + usernameVariable: 'github_user' + passwordVariable: 'github_access_token' ) ] ) { From ec83cf48f5c8f0b48668ba10b35d8c4d6ace3798 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 10:17:21 -0400 Subject: [PATCH 03/32] Fix publish stage. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index d3a375b..c7f0af5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -179,7 +179,7 @@ def publish( [ usernamePassword( credentialsId: 'pyracf-racfu-github-access-token', - usernameVariable: 'github_user' + usernameVariable: 'github_user', passwordVariable: 'github_access_token' ) ] From a1bd218919aec7c73e0e9cab7fedf9ada3ad95e5 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 11:39:48 -0400 Subject: [PATCH 04/32] Generate sha256 checksums. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index c7f0af5..f9798e8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -87,6 +87,9 @@ pipeline { } } stage('Create Python Distribution Metadata') { + when { + expression { params.createRelease == true } + } steps { script { python_versions = params.pythonVersions.split(",") @@ -219,6 +222,13 @@ def publish( ) ).trim() + def checksums_file = "SHASUMS256.txt.asc" + + sh """ + touch dist/${checksums_file} + chtag -t -c ISO8859-1 dist/${checksums_file} + """ + def tar_published = false for (python in python_executables_and_wheels_map.keySet()) { @@ -236,15 +246,26 @@ def publish( """ echo "Uploading '${wheel_default}' as '${wheel_publish}' to '${release}' GitHub release ..." - upload_asset(release_id, wheel_publish) + + echo "Adding sha256 checksum for '${wheel_publish}' to ${checksums_file}..." + sh "sha256sum -t dist/${wheel_publish} >> dist/${checksums_file}" + if (tar_published == false) { + echo "Uploading '${tar_publish}' to '${release}' GitHub release ..." upload_asset(release_id, tar_publish) + + echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." + sh "sha256sum -t dist/${tar_publish} >> dist/${checksums_file}" + tar_published = true } else { sh "rm dist/${tar_publish}" } + + echo "Uploading '${checksums_file}' to '${release}' GitHub release ..." + upload_asset(release_id, checksums_file) } } } From 661955102fd436df611fa7a9828fb64711dd00de Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 12:15:10 -0400 Subject: [PATCH 05/32] Fix checksum generation and upload. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f9798e8..d5d8abb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -229,44 +229,45 @@ def publish( chtag -t -c ISO8859-1 dist/${checksums_file} """ + def python = python_executables_and_wheels_map.keySet()[-1] + def tar_publish = python_executables_and_wheels_map[python]["tarPublish"] def tar_published = false for (python in python_executables_and_wheels_map.keySet()) { def wheel_default = python_executables_and_wheels_map[python]["wheelDefault"] def wheel_publish = python_executables_and_wheels_map[python]["wheelPublish"] - def tar_publish = python_executables_and_wheels_map[python]["tarPublish"] - echo "Cleaning repo and building '${wheel_default}' ..." + echo "Building '${wheel_default}' ..." sh """ git clean -fdx ${python} -m pip install build>=1.2.2 - ${python} -m build + ${python} -m build -w mv dist/${wheel_default} dist/${wheel_publish} """ + if (tar_published == false) { + echo "Building '${tar_publish}' ..." + sh "${python} -m build -s" + + tar_published = true + } + echo "Uploading '${wheel_default}' as '${wheel_publish}' to '${release}' GitHub release ..." upload_asset(release_id, wheel_publish) echo "Adding sha256 checksum for '${wheel_publish}' to ${checksums_file}..." sh "sha256sum -t dist/${wheel_publish} >> dist/${checksums_file}" + } - if (tar_published == false) { - echo "Uploading '${tar_publish}' to '${release}' GitHub release ..." - upload_asset(release_id, tar_publish) + echo "Uploading '${tar_publish}' to '${release}' GitHub release ..." + upload_asset(release_id, tar_publish) - echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." - sh "sha256sum -t dist/${tar_publish} >> dist/${checksums_file}" - - tar_published = true - } - else { - sh "rm dist/${tar_publish}" - } + echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." + sh "sha256sum -t dist/${tar_publish} >> dist/${checksums_file}" - echo "Uploading '${checksums_file}' to '${release}' GitHub release ..." - upload_asset(release_id, checksums_file) - } + echo "Uploading '${checksums_file}' to '${release}' GitHub release ..." + upload_asset(release_id, checksums_file) } } From 050202ff6ca77106801a13251f7842271813dbc3 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 12:18:35 -0400 Subject: [PATCH 06/32] Fix checksum generation and upload. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d5d8abb..2498875 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -228,9 +228,8 @@ def publish( touch dist/${checksums_file} chtag -t -c ISO8859-1 dist/${checksums_file} """ - - def python = python_executables_and_wheels_map.keySet()[-1] - def tar_publish = python_executables_and_wheels_map[python]["tarPublish"] + + def tar_publish def tar_published = false for (python in python_executables_and_wheels_map.keySet()) { @@ -247,6 +246,7 @@ def publish( """ if (tar_published == false) { + tar_publish = python_executables_and_wheels_map[python]["tarPublish"] echo "Building '${tar_publish}' ..." sh "${python} -m build -s" From a5292ea8de67dfcb8ecdf2b2101b3d4a4960831d Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 13:33:24 -0400 Subject: [PATCH 07/32] Add GitHub Actions & cleanup checksums file. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 21 +++++++++++++++++++++ .github/workflows/cppcheck.yml | 13 +++++++++++++ .github/workflows/unittest.yml | 13 +++++++++++++ .pre-commit-config.yaml | 1 - Jenkinsfile | 8 +++++--- 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/clang-format.yml create mode 100644 .github/workflows/cppcheck.yml create mode 100644 .github/workflows/unittest.yml diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..4c52365 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,21 @@ +name: clang-format +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: cpp-linter/cpp-linter-action@v2 + id: clang-format + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + style: 'file' # Use .clang-format config file + tidy-checks: '' # Use .clang-tidy config file + # only 'update' a single comment in a pull request thread. + thread-comments: ${{ github.event_name == 'pull_request' && 'update' }} + - name: Fail fast?! + if: steps.linter.outputs.checks-failed > 0 + run: exit 1 diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml new file mode 100644 index 0000000..89bbc49 --- /dev/null +++ b/.github/workflows/cppcheck.yml @@ -0,0 +1,13 @@ +name: cppcheck +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install cppcheck + run: sudo apt-get install -y cppcheck + - name: Run cppcheck + run: make check diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 0000000..5474e46 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,13 @@ +name: cppcheck +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install clang + run: sudo apt-get install -y clang + - name: unittest + run: make test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d11e2e..368709b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,6 @@ repos: hooks: - id: clang-format files: \.(cpp|hpp|c|h) - exclude: ^externals/|^tests/unity/ - repo: local hooks: - id: cppcheck diff --git a/Jenkinsfile b/Jenkinsfile index 2498875..2423dd5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -232,6 +232,9 @@ def publish( def tar_publish def tar_published = false + echo "Cleaning repo ..." + sh "git clean -fdx" + for (python in python_executables_and_wheels_map.keySet()) { def wheel_default = python_executables_and_wheels_map[python]["wheelDefault"] def wheel_publish = python_executables_and_wheels_map[python]["wheelPublish"] @@ -239,7 +242,6 @@ def publish( echo "Building '${wheel_default}' ..." sh """ - git clean -fdx ${python} -m pip install build>=1.2.2 ${python} -m build -w mv dist/${wheel_default} dist/${wheel_publish} @@ -257,14 +259,14 @@ def publish( upload_asset(release_id, wheel_publish) echo "Adding sha256 checksum for '${wheel_publish}' to ${checksums_file}..." - sh "sha256sum -t dist/${wheel_publish} >> dist/${checksums_file}" + sh "cd dist && sha256sum -t ${wheel_publish} >> ${checksums_file}" } echo "Uploading '${tar_publish}' to '${release}' GitHub release ..." upload_asset(release_id, tar_publish) echo "Adding sha256 checksum for '${tar_publish}' to ${checksums_file}..." - sh "sha256sum -t dist/${tar_publish} >> dist/${checksums_file}" + sh "cd dist && sha256sum -t ${tar_publish} >> ${checksums_file}" echo "Uploading '${checksums_file}' to '${release}' GitHub release ..." upload_asset(release_id, checksums_file) From 5a342982ee61bf21807587bbb730f639129e3b9f Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 15:25:23 -0400 Subject: [PATCH 08/32] Debug GitHub actions workflows. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 18 +++++------------- .../workflows/{unittest.yml => unit-test.yml} | 4 ++-- Makefile | 6 ++++++ racfu/irrseq00/profile_extractor.cpp | 1 - racfu/irrseq00/profile_post_processor.cpp | 1 - racfu/irrsmo00/irrsmo00_error.hpp | 1 + racfu/logger.cpp | 1 + racfu/racfu_error.hpp | 1 + racfu/security_admin.cpp | 5 ++--- setup.py | 3 +++ tests/irrseq00/test_irrseq00.cpp | 1 - tests/mock/irrseq00.cpp | 1 - 12 files changed, 21 insertions(+), 22 deletions(-) rename .github/workflows/{unittest.yml => unit-test.yml} (86%) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 4c52365..26888c7 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -6,16 +6,8 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cpp-linter/cpp-linter-action@v2 - id: clang-format - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - style: 'file' # Use .clang-format config file - tidy-checks: '' # Use .clang-tidy config file - # only 'update' a single comment in a pull request thread. - thread-comments: ${{ github.event_name == 'pull_request' && 'update' }} - - name: Fail fast?! - if: steps.linter.outputs.checks-failed > 0 - run: exit 1 + - uses: actions/checkout@v3 + - name: Install clang-format + run: sudo apt-get install -y clang-format + - name: clang-format + run: make lint diff --git a/.github/workflows/unittest.yml b/.github/workflows/unit-test.yml similarity index 86% rename from .github/workflows/unittest.yml rename to .github/workflows/unit-test.yml index 5474e46..91cad46 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unit-test.yml @@ -1,4 +1,4 @@ -name: cppcheck +name: unit-test on: [push, pull_request] jobs: build: @@ -9,5 +9,5 @@ jobs: - uses: actions/checkout@v3 - name: Install clang run: sudo apt-get install -y clang - - name: unittest + - name: unit-test run: make test diff --git a/Makefile b/Makefile index 7a01be1..0f2954f 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ ifeq ($(UNAME), OS/390) ASFLAGS = -mGOFF -I$(IRRSEQ00_SRC) CFLAGS = \ -std=$(CXXSTANDARD) -m64 -fzos-le-char-mode=ascii \ + -D_POSIX_C_SOURCE=200112L \ -I $(SRC) \ -I $(IRRSMO00_SRC) \ -I $(IRRSEQ00_SRC) \ @@ -51,6 +52,7 @@ else CFLAGS = \ -std=$(CXXSTANDARD) -D__ptr32= \ + -D_POSIX_C_SOURCE=200112L \ -I $(SRC) \ -I $(IRRSMO00_SRC) \ -I $(IRRSEQ00_SRC) \ @@ -131,6 +133,7 @@ check: schema --inconclusive \ --error-exitcode=1 \ -U __TOS_390__ -D __ptr32= \ + -D _POSIX_C_SOURCE=200112L \ -I $(SRC) \ -I $(IRRSMO00_SRC) \ -I $(IRRSEQ00_SRC) \ @@ -139,5 +142,8 @@ check: schema -I $(ZOSLIB) \ $(SRC)/ +lint: + clang-format --Werror --dry-run -i ./**/*.cpp ./**/*.c ./**/*.hpp ./**/*.h + clean: $(RM) $(ARTIFACTS) $(DIST) $(SRC)/racfu_schema.hpp diff --git a/racfu/irrseq00/profile_extractor.cpp b/racfu/irrseq00/profile_extractor.cpp index 718e8c4..bec8546 100644 --- a/racfu/irrseq00/profile_extractor.cpp +++ b/racfu/irrseq00/profile_extractor.cpp @@ -21,7 +21,6 @@ // use ntohl() to convert 16-bit values from big endian to little endian. // On z/OS these macros do nothing since "network order" and z/Architecture are // both big endian. This is only necessary for unit testing off platform. -#define _POSIX_C_SOURCE 200112L #include namespace RACFu { diff --git a/racfu/irrseq00/profile_post_processor.cpp b/racfu/irrseq00/profile_post_processor.cpp index 6a73139..204b4ac 100644 --- a/racfu/irrseq00/profile_post_processor.cpp +++ b/racfu/irrseq00/profile_post_processor.cpp @@ -12,7 +12,6 @@ // use ntohs() to convert 16-bit values from big endian to little endian. // On z/OS these macros do nothing since "network order" and z/Architecture are // both big endian. This is only necessary for unit testing off platform. -#define _POSIX_C_SOURCE 200112L #include #include "key_map.hpp" diff --git a/racfu/irrsmo00/irrsmo00_error.hpp b/racfu/irrsmo00/irrsmo00_error.hpp index e4967aa..0f13989 100644 --- a/racfu/irrsmo00/irrsmo00_error.hpp +++ b/racfu/irrsmo00/irrsmo00_error.hpp @@ -2,6 +2,7 @@ #define __IRRSMO00_ERROR_H_ #include +#include #include namespace RACFu { diff --git a/racfu/logger.cpp b/racfu/logger.cpp index 98a0e38..5281e4e 100644 --- a/racfu/logger.cpp +++ b/racfu/logger.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/racfu/racfu_error.hpp b/racfu/racfu_error.hpp index 8654280..7f5fedf 100644 --- a/racfu/racfu_error.hpp +++ b/racfu/racfu_error.hpp @@ -2,6 +2,7 @@ #define __RACFU_ERROR_H_ #include +#include #include namespace RACFu { diff --git a/racfu/security_admin.cpp b/racfu/security_admin.cpp index b7c0320..7854b29 100644 --- a/racfu/security_admin.cpp +++ b/racfu/security_admin.cpp @@ -1,10 +1,9 @@ #include "security_admin.hpp" -#include - -#define _POSIX_C_SOURCE 200112L #include +#include + #include "irrsmo00.hpp" #include "irrsmo00_error.hpp" #include "profile_extractor.hpp" diff --git a/setup.py b/setup.py index 65d4dc5..fbd28e9 100644 --- a/setup.py +++ b/setup.py @@ -69,6 +69,9 @@ def main(): "ext_modules": [ Extension( "racfu._C", + define_macros=[ + ("_POSIX_C_SOURCE", "200112L") + ], sources=( glob("racfu/**/*.cpp") + glob("racfu/*.cpp") diff --git a/tests/irrseq00/test_irrseq00.cpp b/tests/irrseq00/test_irrseq00.cpp index 7f808ad..bd8d291 100644 --- a/tests/irrseq00/test_irrseq00.cpp +++ b/tests/irrseq00/test_irrseq00.cpp @@ -1,6 +1,5 @@ #include "tests/irrseq00/test_irrseq00.hpp" -#define _POSIX_C_SOURCE 200112L #include #include diff --git a/tests/mock/irrseq00.cpp b/tests/mock/irrseq00.cpp index 36c791d..6b31c88 100644 --- a/tests/mock/irrseq00.cpp +++ b/tests/mock/irrseq00.cpp @@ -13,7 +13,6 @@ // Use htonl() to convert 32-bit values from little endian to big endian. // On z/OS this macro does nothing since "network order" and z/Architecture are // both big endian. This is only necessary for unit testing off platform. -#define _POSIX_C_SOURCE 200112L #include // These globals need to be defined differently depending From ca066be7cf73fffef90928b11edc4956388b6757 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:00:46 -0400 Subject: [PATCH 09/32] Debug workflows. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- tests/unit_test_utilities.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 26888c7..b91aa52 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: sudo apt-get install -y clang-format + run: sudo apt-get install -y clang-format-19 - name: clang-format run: make lint diff --git a/tests/unit_test_utilities.cpp b/tests/unit_test_utilities.cpp index 2b75792..46e0cf1 100644 --- a/tests/unit_test_utilities.cpp +++ b/tests/unit_test_utilities.cpp @@ -277,12 +277,20 @@ void check_arg_pointers(char *raw_request, bool racf_options) { TEST_ASSERT_EQUAL_UINT64(248, arg_pointer[11] - arg_pointer[10]); // ACEE should be 4 bytes TEST_ASSERT_EQUAL_UINT64(4, arg_pointer[12] - arg_pointer[11]); + +#ifdef __TOS_390__ // result buffer subpool should be 1 byte // Note that the difference between the result buffer pointer pointer // and the result buffer subpool pointer is 0x80000001 as a result // of the high order bit of the result buffer pointer pointer being // set to 0x80000000 to indicate that this is the end of the argument list. TEST_ASSERT_EQUAL_UINT64(0x80000001, arg_pointer[13] - arg_pointer[12]); +#else + // When testing off-platform, just remove the high order bit. + // On Linux systems specifically, this assertion fails for some + // reason when the high order bit is on. + TEST_ASSERT_EQUAL_UINT64(1, (arg_pointer[13] - arg_pointer[12]) & 0x7FFFFFFF); +#endif } /*************************************************************************/ From 14eb8eba9e47aa4a9ad59012a552093ca9dc4eba Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:12:39 -0400 Subject: [PATCH 10/32] Try to install clang-format 20 in workflow. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index b91aa52..20af5f5 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: sudo apt-get install -y clang-format-19 + run: sudo apt-get install -y clang-format-20 - name: clang-format run: make lint From 867d5ea968511b5529d5496308419abe7bd0788e Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:19:52 -0400 Subject: [PATCH 11/32] Debug clang-format. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 20af5f5..edbb58d 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: sudo apt-get install -y clang-format-20 + run: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 20 - name: clang-format run: make lint From 13edace5fc83b10591868f1bd36c99b59582db5c Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:22:54 -0400 Subject: [PATCH 12/32] Debug clang-format. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index edbb58d..6350223 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 20 + run: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 20 all && clang-format --version - name: clang-format run: make lint From dc560ecb9fa9e1387e6cf920e6c5e5783b2ec440 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:33:30 -0400 Subject: [PATCH 13/32] Debug clang-format. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 6350223..4d7e9bc 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 20 all && clang-format --version + run: sudo apt remove -y clang-format && sudo apt install -y clang-format-19 - name: clang-format run: make lint From aebbbe7aaf662f66fceedcfe8767676b2ba43524 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 16:39:46 -0400 Subject: [PATCH 14/32] Debug clang-format. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- Makefile | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 4d7e9bc..1cf447a 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -8,6 +8,6 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install clang-format - run: sudo apt remove -y clang-format && sudo apt install -y clang-format-19 + run: sudo apt install -y clang-format-19 - name: clang-format run: make lint diff --git a/Makefile b/Makefile index 0f2954f..f3eb342 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,12 @@ CXXSTANDARD = c++14 # JSON Schemas RACFU_SCHEMA = $(shell cat ${PWD}/schema.json | jq -c) +ifeq ($(UNAME), Linux) + CLANG_FORMAT = clang-format-19 +else + CLANG_FORMAT = clang-format +endif + ifeq ($(UNAME), OS/390) AS = as CC = ibm-clang64 @@ -143,7 +149,7 @@ check: schema $(SRC)/ lint: - clang-format --Werror --dry-run -i ./**/*.cpp ./**/*.c ./**/*.hpp ./**/*.h + $(CLANG_FORMAT) --Werror --dry-run -i ./**/*.cpp ./**/*.c ./**/*.hpp ./**/*.h clean: $(RM) $(ARTIFACTS) $(DIST) $(SRC)/racfu_schema.hpp From ac6bead72916bfd7d191cf5bb35c687bcb87d76f Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 18 Apr 2025 17:27:13 -0400 Subject: [PATCH 15/32] Add build badges. Signed-off-by: Leonard Carcaramo --- .github/workflows/{unit-test.yml => test.yml} | 4 ++-- README.md | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) rename .github/workflows/{unit-test.yml => test.yml} (86%) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/test.yml similarity index 86% rename from .github/workflows/unit-test.yml rename to .github/workflows/test.yml index 91cad46..0d06d48 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: unit-test +name: test on: [push, pull_request] jobs: build: @@ -9,5 +9,5 @@ jobs: - uses: actions/checkout@v3 - name: Install clang run: sudo apt-get install -y clang - - name: unit-test + - name: test run: make test diff --git a/README.md b/README.md index 357f8bc..db3ff79 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ ![RACFu Logo](logo.png) +[![test](https://github.com/ambitus/racfu/actions/workflows/test.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/test.yml) +[![cppcheck](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml) +[![clang-format](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml) +[![Version](https://img.shields.io/pypi/v/racfu?label=alpha)](https://pypi.org/project/racfu/#history) +[![Python Versions](https://img.shields.io/pypi/pyversions/racfu)](https://pypi.org/project/racfu/) +[![Downloads](https://img.shields.io/pypi/dm/racfu)](https://pypistats.org/packages/racfu) + A standardized JSON interface for RACF that enables seemless exploitation by programming languages that have a foreign language interface for C/C++ and native JSON support. ## Description From cbc4aa8984344bdf3e00b7dcaa30c7f94d9ebd30 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Mon, 21 Apr 2025 12:21:17 -0400 Subject: [PATCH 16/32] Python cleanup & add Ruff linter. Signed-off-by: Leonard Carcaramo --- .github/workflows/clang-format.yml | 2 +- .github/workflows/cppcheck.yml | 2 +- .github/workflows/ruff.yml | 17 ++++++++ .github/workflows/test.yml | 2 +- README.md | 11 ++++- SECURITY.md | 2 +- python/racfu/racfu.py | 8 ++-- setup.py | 65 ++++++++++++++---------------- 8 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 1cf447a..3f3e981 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install clang-format run: sudo apt install -y clang-format-19 - name: clang-format diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml index 89bbc49..f8a0dd7 100644 --- a/.github/workflows/cppcheck.yml +++ b/.github/workflows/cppcheck.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install cppcheck run: sudo apt-get install -y cppcheck - name: Run cppcheck diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..fc601ba --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,17 @@ +name: ruff +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install ruff + run: python3 -m pip install --upgrade pip ruff + - name: Run ruff + run: ruff ./python/racfu setup.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d06d48..ffff9e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install clang run: sudo apt-get install -y clang - name: test diff --git a/README.md b/README.md index db3ff79..9f22477 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -![RACFu Logo](logo.png) +![RACFu Logo](https://raw.githubusercontent.com/ambitus/racfu/refs/heads/main/logo.png) [![test](https://github.com/ambitus/racfu/actions/workflows/test.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/test.yml) [![cppcheck](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml) [![clang-format](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml) +[![ruff](https://github.com/ambitus/racfu/actions/workflows/ruff.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/ruff.yml) [![Version](https://img.shields.io/pypi/v/racfu?label=alpha)](https://pypi.org/project/racfu/#history) [![Python Versions](https://img.shields.io/pypi/pyversions/racfu)](https://pypi.org/project/racfu/) [![Downloads](https://img.shields.io/pypi/dm/racfu)](https://pypistats.org/packages/racfu) @@ -15,10 +16,12 @@ As automation becomes more and more prevalent, the need to manage the security e While there are a number of languages that can be used to manage RACF, _(from low level lnaguages like Assembler to higher level languages like REXX)_, the need to be able to easily exploit RACF management functions using existing indurstry standard programming languages and even programming languages that don't exist yet is paramount. The RACFu project is focused on making RACF management functions available to all programming languages that have native JSON support and a foreign language interface for C/C++. This will make it easier to pivot to new tools and programming languages as technology, skills, and business needs continue to evolve in the forseeable future. +RACfu uses [JSON for Modern C++](https://github.com/nlohmann/json) for working with JSON, and [JSON schema validator for JSON for Modern C++](https://github.com/pboettch/json-schema-validator) for [Draft-7 JSON Schema Validation](https://json-schema.org/draft-07). + ## Getting Started -## Minimum z/OS & Language Versions +### Minimum z/OS & Language Versions All versions of **z/OS** and the **IBM Open Enterprise SDK for Python** that are fully supported by IBM are supported by RACFu. * [z/OS Product Lifecycle](https://www.ibm.com/support/pages/lifecycle/search/?q=5655-ZOS,%205650-ZOS) @@ -47,6 +50,10 @@ python3 -m pip install racfu ## Help * [GitHub Discussions](https://github.com/ambitus/racfu/discussions) +## Maintainers +* Leonard Carcaramo: lcarcaramo@ibm.com +* Elijah Swift: Elijah.Swift@ibm.com + ## Authors * Leonard Carcaramo: lcarcaramo@ibm.com diff --git a/SECURITY.md b/SECURITY.md index 3856092..32f18d3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1 +1 @@ -To report a security vulenaribily, use the [Report a vulnerability](https://github.com/ambitus/racfu/security/advisories/new) function in GitHub. \ No newline at end of file +To report a security vulenaribily, use the [Report a vulnerability](https://github.com/ambitus/racfu/security/advisories/new) feature in GitHub. diff --git a/python/racfu/racfu.py b/python/racfu/racfu.py index d27f4a4..bd99ef6 100644 --- a/python/racfu/racfu.py +++ b/python/racfu/racfu.py @@ -21,8 +21,8 @@ def __init__( def racfu(request: dict, debug: bool = False) -> SecurityResult: response = call_racfu(json.dumps(request), debug=debug) return SecurityResult( - request = request, - raw_request = response["raw_request"], - raw_result = response["raw_result"], - result = json.loads(response['result_json']) + request=request, + raw_request=response["raw_request"], + raw_result=response["raw_result"], + result=json.loads(response["result_json"]), ) diff --git a/setup.py b/setup.py index fbd28e9..89f39fa 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def assemble(asm_file: str, asm_directory: Path) -> None: """Python extension assembling underlying objects.""" - obj_file = asm_file.split(".")[0]+".o" + obj_file = asm_file.split(".")[0] + ".o" cwd = Path.cwd() source_file = cwd / asm_directory / asm_file obj_file = cwd / "artifacts" / obj_file @@ -27,10 +27,13 @@ def assemble(asm_file: str, asm_directory: Path) -> None: print(mkdir_command) subprocess.run(mkdir_command, shell=True, check=True) - assemble_command = f"as -mGOFF -I{source_file.parents[0]} -o {obj_file} {source_file}" + assemble_command = ( + f"as -mGOFF -I{source_file.parents[0]} -o {obj_file} {source_file}" + ) print(assemble_command) subprocess.run(assemble_command, shell=True, check=True) - + + def build_json_schema_header() -> None: schema_absolute_path = Path.cwd() / "schema.json" with open(schema_absolute_path, "r") as f: @@ -41,15 +44,16 @@ def build_json_schema_header() -> None: "\n".join( [ "#ifndef __RACFU_SCHEMA_H_", - "#define __RACFU_SCHEMA_H_", + "#define __RACFU_SCHEMA_H_", "", - f"#define RACFU_SCHEMA R\"({schema})\"_json", + f'#define RACFU_SCHEMA R"({schema})"_json', "", - "#endif" + "#endif", ] ) ) + class build_with_asm_ext(build_ext): def run(self): os.environ["CC"] = "ibm-clang64" @@ -60,6 +64,7 @@ def run(self): assemble("irrseq00.s", racfu_source_path / "irrseq00") super().run() + def main(): """Python extension build entrypoint.""" cwd = Path.cwd() @@ -67,37 +72,27 @@ def main(): build_json_schema_header() setup_args = { "ext_modules": [ - Extension( - "racfu._C", - define_macros=[ - ("_POSIX_C_SOURCE", "200112L") - ], - sources=( - glob("racfu/**/*.cpp") - + glob("racfu/*.cpp") - + glob("externals/json-schema-validator/*.cpp") - + ["racfu/python/_racfu.c"] - ), - include_dirs=( - glob("racfu/**/") - + [ - "racfu", - "externals/json", - "externals/json-schema-validator" - ] - ), - extra_link_args = [ - "-m64", - "-Wl,-b,edit=no" - ], - extra_objects = [ - f"{assembled_object_path}" - ] - ) - ], - "cmdclass": {"build_ext": build_with_asm_ext} + Extension( + "racfu._C", + define_macros=[("_POSIX_C_SOURCE", "200112L")], + sources=( + glob("racfu/**/*.cpp") + + glob("racfu/*.cpp") + + glob("externals/json-schema-validator/*.cpp") + + ["racfu/python/_racfu.c"] + ), + include_dirs=( + glob("racfu/**/") + + ["racfu", "externals/json", "externals/json-schema-validator"] + ), + extra_link_args=["-m64", "-Wl,-b,edit=no"], + extra_objects=[f"{assembled_object_path}"], + ) + ], + "cmdclass": {"build_ext": build_with_asm_ext}, } setup(**setup_args) + if __name__ == "__main__": main() From 5ca5b6f5f404886b714ddb58add83e4e0b0f387b Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Mon, 21 Apr 2025 12:25:57 -0400 Subject: [PATCH 17/32] Fix ruff workflow Signed-off-by: Leonard Carcaramo --- .github/workflows/ruff.yml | 2 +- README.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index fc601ba..a9903da 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -14,4 +14,4 @@ jobs: - name: Install ruff run: python3 -m pip install --upgrade pip ruff - name: Run ruff - run: ruff ./python/racfu setup.py + run: ruff check ./python/racfu setup.py diff --git a/README.md b/README.md index 9f22477..2f77644 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ python3 -m pip install racfu ## Help * [GitHub Discussions](https://github.com/ambitus/racfu/discussions) -## Maintainers -* Leonard Carcaramo: lcarcaramo@ibm.com -* Elijah Swift: Elijah.Swift@ibm.com - ## Authors * Leonard Carcaramo: lcarcaramo@ibm.com * Elijah Swift: Elijah.Swift@ibm.com * Frank De Gilio: degilio@us.ibm.com * Joe Bostian: jbostian@ibm.com + +## Maintainers +* Leonard Carcaramo: lcarcaramo@ibm.com +* Elijah Swift: Elijah.Swift@ibm.com From 8329da8ec3bd536692f7b181938379af5ea570a2 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Mon, 21 Apr 2025 12:51:29 -0400 Subject: [PATCH 18/32] Python style fixes. Signed-off-by: Leonard Carcaramo --- python/racfu/__init__.py | 3 ++- python/racfu/racfu.py | 4 ++++ setup.py | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/python/racfu/__init__.py b/python/racfu/__init__.py index ab326ae..5ae3879 100644 --- a/python/racfu/__init__.py +++ b/python/racfu/__init__.py @@ -1 +1,2 @@ -from .racfu import SecurityResult, racfu +from .racfu import SecurityResult as SecurityResult +from .racfu import racfu as racfu diff --git a/python/racfu/racfu.py b/python/racfu/racfu.py index bd99ef6..6e01c72 100644 --- a/python/racfu/racfu.py +++ b/python/racfu/racfu.py @@ -1,3 +1,5 @@ +"""Python code to interface with RACFu.""" + import json from typing import Any @@ -5,6 +7,7 @@ class SecurityResult: + """Container for RACFu result information.""" def __init__( self, request: dict, @@ -19,6 +22,7 @@ def __init__( def racfu(request: dict, debug: bool = False) -> SecurityResult: + """Call RACFu Python extenion.""" response = call_racfu(json.dumps(request), debug=debug) return SecurityResult( request=request, diff --git a/setup.py b/setup.py index 89f39fa..130b3e0 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def assemble(asm_file: str, asm_directory: Path) -> None: - """Python extension assembling underlying objects.""" + """Assemble assembler code.""" obj_file = asm_file.split(".")[0] + ".o" cwd = Path.cwd() source_file = cwd / asm_directory / asm_file @@ -34,7 +34,8 @@ def assemble(asm_file: str, asm_directory: Path) -> None: subprocess.run(assemble_command, shell=True, check=True) -def build_json_schema_header() -> None: +def generate_json_schema_header() -> None: + """Generate RACFu JSON schema header.""" schema_absolute_path = Path.cwd() / "schema.json" with open(schema_absolute_path, "r") as f: schema = json.dumps(json.load(f), separators=(",", ":")) @@ -55,6 +56,7 @@ def build_json_schema_header() -> None: class build_with_asm_ext(build_ext): + """Build Python extension that includes assembler code.""" def run(self): os.environ["CC"] = "ibm-clang64" os.environ["CFLAGS"] = "-std=c99" @@ -69,7 +71,7 @@ def main(): """Python extension build entrypoint.""" cwd = Path.cwd() assembled_object_path = cwd / "artifacts" / "irrseq00.o" - build_json_schema_header() + generate_json_schema_header() setup_args = { "ext_modules": [ Extension( From ed8f1388d90c9bcea1dacf65eb2b38fc5824e456 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 24 Apr 2025 16:51:36 -0400 Subject: [PATCH 19/32] Fuzzing, thread safety, update libraries. Signed-off-by: Leonard Carcaramo --- .github/workflows/fuzz.yml | 13 + .pre-commit-config.yaml | 8 + Makefile | 20 + NOTICES | 4 +- README.md | 1 + externals/json/nlohmann/LICENSE.MIT | 2 +- externals/json/nlohmann/json.hpp | 5313 ++++++++++------- fuzz/fuzz.cpp | 6 + racfu/python/_racfu.c | 21 +- racfu/racfu.cpp | 4 +- racfu/racfu.h | 2 +- racfu/security_admin.cpp | 9 +- racfu/security_admin.hpp | 2 +- tests/irrsmo00/test_irrsmo00.cpp | 9 +- tests/unit_test_utilities.cpp | 22 +- tests/unity/LICENSE.txt | 2 +- tests/unity/unity.c | 679 ++- tests/unity/unity.h | 63 +- tests/unity/unity_internals.h | 254 +- .../validation/test_parameter_validation.cpp | 6 +- 20 files changed, 3924 insertions(+), 2516 deletions(-) create mode 100644 .github/workflows/fuzz.yml create mode 100644 fuzz/fuzz.cpp diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..8c397fc --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,13 @@ +name: fuzz +on: [push, pull_request] +jobs: + build: + strategy: + fail-fast: false + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install clang + run: sudo apt-get install -y clang + - name: fuzz + run: make fuzz diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 368709b..822526a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,14 @@ repos: pass_filenames: false always_run: true verbose: true + - repo: local + hooks: + - id: fuzz + name: fuzz + entry: make fuzz + language: system + pass_filenames: false + always_run: true - repo: local hooks: - id: unit-test diff --git a/Makefile b/Makefile index f3eb342..4eaaa4b 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,11 @@ else -I $(ZOSLIB) endif +FUZZFLGS = \ + -fsanitize=fuzzer \ + -fsanitize=undefined \ + -fsanitize=address + RM = rm -rf all: racfu @@ -118,6 +123,21 @@ test: clean mkdirs schema && $(CXX) $(LDFLAGS) *.o -o $(DIST)/test_runner $(DIST)/test_runner +fuzz: clean mkdirs schema + cd $(ARTIFACTS) \ + && $(CXX) -c $(CFLAGS) $(TFLAGS) $(FUZZFLGS) \ + ${PWD}/fuzz/fuzz.cpp \ + $(TESTS)/mock/*.cpp \ + $(SRCZOSLIB) \ + $(SRC)/*.cpp \ + $(IRRSMO00_SRC)/*.cpp \ + $(IRRSEQ00_SRC)/*.cpp \ + $(KEY_MAP)/*.cpp \ + $(VALIDATION)/*.cpp \ + $(JSON_SCHEMA)/*.cpp \ + && $(CXX) $(LDFLAGS) $(FUZZFLGS) *.o -o $(DIST)/fuzz + $(DIST)/fuzz -runs=65536 -artifact_prefix=$(ARTIFACTS)/ + fvt: python3 $(TESTS)/fvt/fvt.py diff --git a/NOTICES b/NOTICES index 5171d3c..999f4a7 100644 --- a/NOTICES +++ b/NOTICES @@ -1,8 +1,8 @@ -JSON for Modern C++ - 3.11.3 +JSON for Modern C++ - 3.12.0 MIT License -Copyright (c) 2013-2022 Niels Lohmann +Copyright (c) 2013-2025 Niels Lohmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2f77644..872720f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![test](https://github.com/ambitus/racfu/actions/workflows/test.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/test.yml) [![cppcheck](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml) [![clang-format](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml) +[fuzz](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/fuzz.yml) [![ruff](https://github.com/ambitus/racfu/actions/workflows/ruff.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/ruff.yml) [![Version](https://img.shields.io/pypi/v/racfu?label=alpha)](https://pypi.org/project/racfu/#history) [![Python Versions](https://img.shields.io/pypi/pyversions/racfu)](https://pypi.org/project/racfu/) diff --git a/externals/json/nlohmann/LICENSE.MIT b/externals/json/nlohmann/LICENSE.MIT index 1c1f7a6..a1dacc8 100644 --- a/externals/json/nlohmann/LICENSE.MIT +++ b/externals/json/nlohmann/LICENSE.MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2013-2022 Niels Lohmann +Copyright (c) 2013-2025 Niels Lohmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/externals/json/nlohmann/json.hpp b/externals/json/nlohmann/json.hpp index 8b72ea6..82d69f7 100644 --- a/externals/json/nlohmann/json.hpp +++ b/externals/json/nlohmann/json.hpp @@ -1,9 +1,9 @@ // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT /****************************************************************************\ @@ -34,10 +34,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -47,10 +47,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -59,20 +59,24 @@ #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 #warning "Already included a different version of the library!" #endif #endif #endif #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_DIAGNOSTIC_POSITIONS + #define JSON_DIAGNOSTIC_POSITIONS 0 +#endif + #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 #endif @@ -83,6 +87,12 @@ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS #endif +#if JSON_DIAGNOSTIC_POSITIONS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS +#endif + #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp #else @@ -94,14 +104,15 @@ #endif // Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) #define NLOHMANN_JSON_ABI_TAGS \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) // Construct the namespace version component #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ @@ -149,10 +160,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -162,6 +173,9 @@ #include // forward_list #include // inserter, front_inserter, end #include // map +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif #include // string #include // tuple, make_tuple #include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible @@ -172,10 +186,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -192,10 +206,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -208,10 +222,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -220,10 +234,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -233,10 +247,10 @@ // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -320,11 +334,11 @@ NLOHMANN_JSON_NAMESPACE_END // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson // SPDX-License-Identifier: MIT /* Hedley - https://nemequ.github.io/hedley @@ -2384,15 +2398,20 @@ JSON_HEDLEY_DIAGNOSTIC_POP // C++ language standard detection // if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) + #define JSON_HAS_CPP_23 + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) #define JSON_HAS_CPP_20 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif // the cpp 11 flag is always specified because it is the minimal required version @@ -2568,7 +2587,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ @@ -2580,7 +2601,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [&j](const std::pair& ej_pair) -> bool \ @@ -2743,42 +2766,146 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; #define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; /*! @brief macro @def NLOHMANN_DEFINE_TYPE_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } /*! @brief macro @def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE @since version 3.9.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ */ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.11.3 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ +*/ #define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + template::value, int> = 0> \ + void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE +@since version 3.12.0 +@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ +*/ +#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ + template::value, int> = 0> \ + void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } // inspired from https://stackoverflow.com/a/26745591 -// allows to call any std function as if (e.g. with begin): +// allows calling any std function as if (e.g., with begin): // using std::begin; begin(x); // // it allows using the detected idiom to retrieve the return type @@ -2939,10 +3066,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3014,10 +3141,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3056,10 +3183,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-FileCopyrightText: 2018 The Abseil Authors // SPDX-License-Identifier: MIT @@ -3219,7 +3346,7 @@ struct static_const #endif template -inline constexpr std::array make_array(Args&& ... args) +constexpr std::array make_array(Args&& ... args) { return std::array {{static_cast(std::forward(args))...}}; } @@ -3230,27 +3357,27 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // numeric_limits +#include // char_traits +#include // tuple #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval -#include // tuple -#include // char_traits // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3293,7 +3420,7 @@ struct iterator_traits template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types + : iterator_types { }; @@ -3315,10 +3442,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3335,10 +3462,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -3359,10 +3486,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ @@ -3624,7 +3751,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3648,7 +3775,7 @@ struct char_traits : std::char_traits static constexpr int_type eof() noexcept { - return static_cast(EOF); + return static_cast(std::char_traits::eof()); } }; @@ -3674,19 +3801,19 @@ struct is_default_constructible : std::is_default_constructible {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction, is_default_constructible> {}; + : conjunction, is_default_constructible> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_default_constructible> - : conjunction...> {}; + : conjunction...> {}; template struct is_constructible : std::is_constructible {}; @@ -3884,8 +4011,8 @@ is_detected::value&& // special case for types like std::filesystem::path whose iterator's value_type are themselves // c.f. https://github.com/nlohmann/json/pull/3073 !std::is_same>::value&& - is_complete_type < - detected_t>::value >> +is_complete_type < +detected_t>::value >> { using value_type = range_value_t; @@ -4008,12 +4135,12 @@ using is_usable_as_key_type = typename std::conditional < template> using is_usable_as_basic_json_key_type = typename std::conditional < - is_usable_as_key_type::value - && !is_json_iterator_of::value, - std::true_type, - std::false_type >::type; + is_usable_as_key_type::value + && !is_json_iterator_of::value, + std::true_type, + std::false_type >::type; template using detect_erase_with_key_type = decltype(std::declval().erase(std::declval())); @@ -4147,7 +4274,7 @@ struct value_in_range_of_impl1 }; template -inline constexpr bool value_in_range_of(T val) +constexpr bool value_in_range_of(T val) { return value_in_range_of_impl1::test(val); } @@ -4163,7 +4290,7 @@ namespace impl { template -inline constexpr bool is_c_string() +constexpr bool is_c_string() { using TUnExt = typename std::remove_extent::type; using TUnCVExt = typename std::remove_cv::type; @@ -4191,7 +4318,7 @@ namespace impl { template -inline constexpr bool is_transparent() +constexpr bool is_transparent() { return is_detected::value; } @@ -4210,10 +4337,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4358,6 +4485,18 @@ inline OutStringType concat(Args && ... args) NLOHMANN_JSON_NAMESPACE_END +// With -Wweak-vtables, Clang will complain about the exception classes as they +// have no out-of-line virtual method definitions and their vtable will be +// emitted in every translation unit. This issue cannot be fixed with a +// header-only library as there is no implementation file to move these +// functions to. As a result, we suppress this warning here to avoid client +// code to stumble over this. See https://github.com/nlohmann/json/issues/4087 +// for a discussion. +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wweak-vtables" +#endif + NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4452,16 +4591,34 @@ class exception : public std::exception { return concat(a, '/', detail::escape(b)); }); - return concat('(', str, ") "); + + return concat('(', str, ") ", get_byte_positions(leaf_element)); #else - static_cast(leaf_element); - return ""; + return get_byte_positions(leaf_element); #endif } private: /// an exception object as storage for error messages std::runtime_error m; +#if JSON_DIAGNOSTIC_POSITIONS + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos)) + { + return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); + } + return ""; + } +#else + template + static std::string get_byte_positions(const BasicJsonType* leaf_element) + { + static_cast(leaf_element); + return ""; + } +#endif }; /// @brief exception indicating a parse error @@ -4589,6 +4746,10 @@ class other_error : public exception } // namespace detail NLOHMANN_JSON_NAMESPACE_END +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + // #include // #include @@ -4596,10 +4757,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4620,10 +4781,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -4640,7 +4801,7 @@ namespace std_fs = std::experimental::filesystem; } // namespace detail NLOHMANN_JSON_NAMESPACE_END #elif JSON_HAS_FILESYSTEM -#include +#include // NOLINT(build/c++17) NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { @@ -4670,6 +4831,24 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) n = nullptr; } +#ifdef JSON_HAS_CPP_17 +#ifndef JSON_USE_IMPLICIT_CONVERSIONS +template +void from_json(const BasicJsonType& j, std::optional& opt) +{ + if (j.is_null()) + { + opt = std::nullopt; + } + else + { + opt.emplace(j.template get()); + } +} + +#endif // JSON_USE_IMPLICIT_CONVERSIONS +#endif // JSON_HAS_CPP_17 + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -4817,6 +4996,54 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } } +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + arr[i1][i2] = j.at(i1).at(i2).template get(); + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get(); + } + } + } +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i1 = 0; i1 < N1; ++i1) + { + for (std::size_t i2 = 0; i2 < N2; ++i2) + { + for (std::size_t i3 = 0; i3 < N3; ++i3) + { + for (std::size_t i4 = 0; i4 < N4; ++i4) + { + arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get(); + } + } + } + } +} + template inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { @@ -4902,7 +5129,7 @@ void()) template < typename BasicJsonType, typename T, std::size_t... Idx > std::array from_json_inplace_array_impl(BasicJsonType&& j, - identity_tag> /*unused*/, index_sequence /*unused*/) + identity_tag> /*unused*/, index_sequence /*unused*/) { return { { std::forward(j).at(Idx).template get()... } }; } @@ -5006,6 +5233,12 @@ std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence< return std::make_tuple(std::forward(j).at(Idx).template get()...); } +template +std::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/) +{ + return {}; +} + template < typename BasicJsonType, class A1, class A2 > std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) { @@ -5091,7 +5324,12 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { JSON_THROW(type_error::create(302, concat("type must be string, but is ", j.type_name()), &j)); } - p = *j.template get_ptr(); + const auto& s = *j.template get_ptr(); +#ifdef JSON_HAS_CPP_20 + p = std_fs::path(std::u8string_view(reinterpret_cast(s.data()), s.size())); +#else + p = std_fs::u8path(s); // accepts UTF-8 encoded std::string in C++17, deprecated in C++20 +#endif } #endif @@ -5126,14 +5364,20 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT +// #include +// JSON_HAS_CPP_17 +#ifdef JSON_HAS_CPP_17 + #include // optional +#endif + #include // copy #include // begin, end #include // string @@ -5146,17 +5390,16 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include // size_t -#include // input_iterator_tag -#include // string, to_string +#include // forward_iterator_tag #include // tuple_size, get, tuple_element #include // move @@ -5168,20 +5411,53 @@ NLOHMANN_JSON_NAMESPACE_END // #include -// #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t +#include // string, to_string + +// #include NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { -template -void int_to_string( string_type& target, std::size_t value ) +template +void int_to_string(StringType& target, std::size_t value) { // For ADL using std::to_string; target = to_string(value); } + +template +StringType to_string(std::size_t value) +{ + StringType result; + int_to_string(result, value); + return result; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + template class iteration_proxy_value { public: @@ -5189,7 +5465,7 @@ template class iteration_proxy_value using value_type = iteration_proxy_value; using pointer = value_type *; using reference = value_type &; - using iterator_category = std::input_iterator_tag; + using iterator_category = std::forward_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: @@ -5369,7 +5645,7 @@ namespace std #endif template class tuple_size<::nlohmann::detail::iteration_proxy_value> // NOLINT(cert-dcl58-cpp) - : public std::integral_constant {}; + : public std::integral_constant {}; template class tuple_element> // NOLINT(cert-dcl58-cpp) @@ -5390,8 +5666,6 @@ class tuple_element> inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; #endif -// #include - // #include // #include @@ -5637,6 +5911,22 @@ struct external_constructor // to_json // ///////////// +#ifdef JSON_HAS_CPP_17 +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::optional& opt) +{ + if (opt.has_value()) + { + j = *opt; + } + else + { + j = nullptr; + } +} +#endif + template::value, int> = 0> inline void to_json(BasicJsonType& j, T b) noexcept @@ -5697,7 +5987,8 @@ template::type; - external_constructor::construct(j, static_cast(e)); + static constexpr value_t integral_value_t = std::is_unsigned::value ? value_t::number_unsigned : value_t::number_integer; + external_constructor::construct(j, static_cast(e)); } #endif // JSON_DISABLE_ENUM_SERIALIZATION @@ -5782,6 +6073,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } +template +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) +{ + using array_t = typename BasicJsonType::array_t; + j = array_t(); +} + template::value, int > = 0> inline void to_json(BasicJsonType& j, const T& t) { @@ -5792,7 +6090,12 @@ inline void to_json(BasicJsonType& j, const T& t) template inline void to_json(BasicJsonType& j, const std_fs::path& p) { - j = p.string(); +#ifdef JSON_HAS_CPP_20 + const std::u8string s = p.u8string(); + j = std::string(s.begin(), s.end()); +#else + j = p.u8string(); // returns std::string in C++17 +#endif } #endif @@ -5867,10 +6170,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -5979,10 +6282,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6112,10 +6415,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6132,16 +6435,19 @@ NLOHMANN_JSON_NAMESPACE_END #include // char_traits, string #include // make_pair, move #include // vector +#ifdef __cpp_lib_byteswap + #include //byteswap +#endif // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -6161,6 +6467,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // istream #endif // JSON_NO_IO +// #include + // #include // #include @@ -6208,6 +6516,13 @@ class file_input_adapter return std::fgetc(m_file); } + // returns the number of characters successfully read + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + return fread(dest, 1, sizeof(T) * count, m_file); + } + private: /// the file pointer to read from std::FILE* m_file; @@ -6267,6 +6582,17 @@ class input_stream_adapter return res; } + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto res = static_cast(sb->sgetn(reinterpret_cast(dest), static_cast(count * sizeof(T)))); + if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T))) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + private: /// the associated input stream std::istream* is = nullptr; @@ -6298,6 +6624,26 @@ class iterator_input_adapter return char_traits::eof(); } + // for general iterators, we cannot really do something better than falling back to processing the range one-by-one + template + std::size_t get_elements(T* dest, std::size_t count = 1) + { + auto* ptr = reinterpret_cast(dest); + for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index) + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + ptr[read_index] = static_cast(*current); + std::advance(current, 1); + } + else + { + return read_index; + } + } + return count * sizeof(T); + } + private: IteratorType current; IteratorType end; @@ -6461,6 +6807,13 @@ class wide_string_input_adapter return utf8_bytes[utf8_bytes_index++]; } + // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here + template + std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1) + { + JSON_THROW(parse_error::create(112, 1, "wide string type cannot be interpreted as binary data", nullptr)); + } + private: BaseInputAdapter base_adapter; @@ -6557,10 +6910,17 @@ typename container_input_adapter_factory_impl::container_input_adapter_factory::create(container); } +// specialization for std::string +using string_input_adapter_type = decltype(input_adapter(std::declval())); + #ifndef JSON_NO_IO // Special cases with fast paths inline file_input_adapter input_adapter(std::FILE* file) { + if (file == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } return file_input_adapter(file); } @@ -6587,9 +6947,13 @@ template < typename CharT, int >::type = 0 > contiguous_bytes_input_adapter input_adapter(CharT b) { + if (b == nullptr) + { + JSON_THROW(parse_error::create(101, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } auto length = std::strlen(reinterpret_cast(b)); const auto* ptr = reinterpret_cast(b); - return input_adapter(ptr, ptr + length); + return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck] } template @@ -6635,2383 +6999,2653 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT #include #include // string +#include // enable_if_t #include // move #include // vector // #include +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // localeconv +#include // size_t +#include // snprintf +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // char_traits, string +#include // move +#include // vector + +// #include + +// #include + // #include -// #include +// #include NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ -/*! -@brief SAX interface +/////////// +// lexer // +/////////// -This class describes the SAX interface used by @ref nlohmann::json::sax_parse. -Each function is called in different situations while the input is parsed. The -boolean return value informs the parser whether to continue processing the -input. -*/ template -struct json_sax +class lexer_base { - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @brief a null value was read - @return whether parsing should proceed - */ - virtual bool null() = 0; - - /*! - @brief a boolean value was read - @param[in] val boolean value - @return whether parsing should proceed - */ - virtual bool boolean(bool val) = 0; - - /*! - @brief an integer number was read - @param[in] val integer value - @return whether parsing should proceed - */ - virtual bool number_integer(number_integer_t val) = 0; - - /*! - @brief an unsigned integer number was read - @param[in] val unsigned integer value - @return whether parsing should proceed - */ - virtual bool number_unsigned(number_unsigned_t val) = 0; - - /*! - @brief a floating-point number was read - @param[in] val floating-point value - @param[in] s raw token value - @return whether parsing should proceed - */ - virtual bool number_float(number_float_t val, const string_t& s) = 0; - - /*! - @brief a string value was read - @param[in] val string value - @return whether parsing should proceed - @note It is safe to move the passed string value. - */ - virtual bool string(string_t& val) = 0; - - /*! - @brief a binary value was read - @param[in] val binary value - @return whether parsing should proceed - @note It is safe to move the passed binary value. - */ - virtual bool binary(binary_t& val) = 0; - - /*! - @brief the beginning of an object was read - @param[in] elements number of object elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_object(std::size_t elements) = 0; - - /*! - @brief an object key was read - @param[in] val object key - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool key(string_t& val) = 0; - - /*! - @brief the end of an object was read - @return whether parsing should proceed - */ - virtual bool end_object() = 0; - - /*! - @brief the beginning of an array was read - @param[in] elements number of array elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_array(std::size_t elements) = 0; - - /*! - @brief the end of an array was read - @return whether parsing should proceed - */ - virtual bool end_array() = 0; - - /*! - @brief a parse error occurred - @param[in] position the position in the input where the error occurs - @param[in] last_token the last read token - @param[in] ex an exception object describing the error - @return whether parsing should proceed (must return false) - */ - virtual bool parse_error(std::size_t position, - const std::string& last_token, - const detail::exception& ex) = 0; + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; - json_sax() = default; - json_sax(const json_sax&) = default; - json_sax(json_sax&&) noexcept = default; - json_sax& operator=(const json_sax&) = default; - json_sax& operator=(json_sax&&) noexcept = default; - virtual ~json_sax() = default; + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case token_type::value_unsigned: + case token_type::value_integer: + case token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } }; - -namespace detail -{ /*! -@brief SAX implementation to create a JSON value from SAX events - -This class implements the @ref json_sax interface and processes the SAX events -to create a JSON value which makes it basically a DOM parser. The structure or -hierarchy of the JSON value is managed by the stack `ref_stack` which contains -a pointer to the respective array or object for each recursion depth. - -After successful parsing, the value that is passed by reference to the -constructor contains the parsed value. +@brief lexical analysis -@tparam BasicJsonType the JSON type +This class organizes the lexical analysis during JSON deserialization. */ -template -class json_sax_dom_parser +template +class lexer : public lexer_base { - public: using number_integer_t = typename BasicJsonType::number_integer_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_float_t = typename BasicJsonType::number_float_t; using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename char_traits::int_type; - /*! - @param[in,out] r reference to a JSON value that is manipulated while - parsing - @param[in] allow_exceptions_ whether parse errors yield exceptions - */ - explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) - : root(r), allow_exceptions(allow_exceptions_) + public: + using token_type = typename lexer_base::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) {} - // make class move-only - json_sax_dom_parser(const json_sax_dom_parser&) = delete; - json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; - json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_parser() = default; + // delete because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~lexer() = default; - bool null() - { - handle_value(nullptr); - return true; - } + private: + ///////////////////// + // locales + ///////////////////// - bool boolean(bool val) + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept { - handle_value(val); - return true; + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } + ///////////////////// + // scan functions + ///////////////////// - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } + /*! + @brief get codepoint from 4 hex characters following `\u` - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. - bool start_object(std::size_t len) + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() { - ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast((static_cast(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast((static_cast(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast((static_cast(current) - 0x57u) << factor); + } + else + { + return -1; + } } - return true; + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; } - bool key(string_t& val) - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); + /*! + @brief check if the next byte(s) are inside a given range - // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); - return true; - } + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. - bool end_object() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_object()); + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. - bool start_array(std::size_t len) + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) { - ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); - if (JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) + for (auto range = ranges.begin(); range != ranges.end(); ++range) { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } } return true; } - bool end_array() - { - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(ref_stack.back()->is_array()); - - ref_stack.back()->set_parents(); - ref_stack.pop_back(); - return true; - } + /*! + @brief scan a string literal - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } + This function scans a string according to Sect. 7 of RFC 8259. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. - constexpr bool is_errored() const - { - return errored; - } + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise - private: - /*! - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements + @note In case of errors, variable error_message contains a textual + description. */ - template - JSON_HEDLEY_RETURNS_NON_NULL - BasicJsonType* handle_value(Value&& v) + token_type scan_string() { - if (ref_stack.empty()) - { - root = BasicJsonType(std::forward(v)); - return &root; - } + // reset token_buffer (ignore opening quote) + reset(); - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); - if (ref_stack.back()->is_array()) + while (true) { - ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - return &(ref_stack.back()->m_data.m_value.array->back()); - } - - JSON_ASSERT(ref_stack.back()->is_object()); - JSON_ASSERT(object_element); - *object_element = BasicJsonType(std::forward(v)); - return object_element; - } + // get next character + switch (get()) + { + // end of file while parsing string + case char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; + // closing quote + case '\"': + { + return token_type::value_string; + } -template -class json_sax_dom_callback_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using parser_callback_t = typename BasicJsonType::parser_callback_t; - using parse_event_t = typename BasicJsonType::parse_event_t; + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; - json_sax_dom_callback_parser(BasicJsonType& r, - const parser_callback_t cb, - const bool allow_exceptions_ = true) - : root(r), callback(cb), allow_exceptions(allow_exceptions_) - { - keep_stack.push_back(true); - } + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 - // make class move-only - json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~json_sax_dom_callback_parser() = default; + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - bool null() - { - handle_value(nullptr); - return true; - } + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); - bool boolean(bool val) - { - handle_value(val); - return true; - } + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast( + // high surrogate occupies the most significant 22 bits + (static_cast(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast(codepoint2) + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result, so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } + // result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } - bool string(string_t& val) - { - handle_value(val); - return true; - } + break; + } - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } - bool start_object(std::size_t len) - { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); - keep_stack.push_back(keep); + break; + } - auto val = handle_value(BasicJsonType::value_t::object, true); - ref_stack.push_back(val.second); + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } - // check object limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); - } + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } - return true; - } + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } - bool key(string_t& val) - { - BasicJsonType k = BasicJsonType(val); + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } - // check callback for key - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); - key_keep_stack.push_back(keep); + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } - // add discarded value at given key and store the reference for later - if (keep && ref_stack.back()) - { - object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); - } + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } - return true; - } + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } - bool end_object() - { - if (ref_stack.back()) - { - if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) - { - // discard object - *ref_stack.back() = discarded; - } - else - { - ref_stack.back()->set_parents(); - } - } + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); + case 0x08: + { + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; + } - if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) - { - // remove discarded value - for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) - { - if (it->is_discarded()) + case 0x09: { - ref_stack.back()->erase(it); - break; + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; } - } - } - return true; - } + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } - bool start_array(std::size_t len) - { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); - keep_stack.push_back(keep); + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } - auto val = handle_value(BasicJsonType::value_t::array, true); - ref_stack.push_back(val.second); + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } - // check array limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != static_cast(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); - } + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } - return true; - } + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } - bool end_array() - { - bool keep = true; - - if (ref_stack.back()) - { - keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); - if (keep) - { - ref_stack.back()->set_parents(); - } - else - { - // discard array - *ref_stack.back() = discarded; - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - // remove discarded value - if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->pop_back(); - } - - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @param[in] v value to add to the JSON value we build during parsing - @param[in] skip_callback whether we should skip calling the callback - function; this is required after start_array() and - start_object() SAX events, because otherwise we would call the - callback function with an empty array or object, respectively. - - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - - @return pair of boolean (whether value should be kept) and pointer (to the - passed value in the ref_stack hierarchy; nullptr if not kept) - */ - template - std::pair handle_value(Value&& v, const bool skip_callback = false) - { - JSON_ASSERT(!keep_stack.empty()); - - // do not handle this value if we know it would be added to a discarded - // container - if (!keep_stack.back()) - { - return {false, nullptr}; - } - - // create value - auto value = BasicJsonType(std::forward(v)); - - // check callback - const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } - // do not handle this value if we just learnt it shall be discarded - if (!keep) - { - return {false, nullptr}; - } + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } - if (ref_stack.empty()) - { - root = std::move(value); - return {true, & root}; - } + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } - // skip this value if we already decided to skip the parent - // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) - if (!ref_stack.back()) - { - return {false, nullptr}; - } + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } - // we now only expect arrays and objects - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } - // array - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); - return {true, & (ref_stack.back()->m_data.m_value.array->back())}; - } + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } - // object - JSON_ASSERT(ref_stack.back()->is_object()); - // check if we should store an element for the current key - JSON_ASSERT(!key_keep_stack.empty()); - const bool store_element = key_keep_stack.back(); - key_keep_stack.pop_back(); + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } - if (!store_element) - { - return {false, nullptr}; - } + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } - JSON_ASSERT(object_element); - *object_element = std::move(value); - return {true, object_element}; - } + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// stack to manage which values to keep - std::vector keep_stack {}; - /// stack to manage which object keys to keep - std::vector key_keep_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// callback function - const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; - /// a discarded value for the callback - BasicJsonType discarded = BasicJsonType::value_t::discarded; -}; + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } -template -class json_sax_acceptor -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } - bool null() - { - return true; - } + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } - bool boolean(bool /*unused*/) - { - return true; - } + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } - bool number_integer(number_integer_t /*unused*/) - { - return true; - } + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } - bool number_unsigned(number_unsigned_t /*unused*/) - { - return true; - } + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } - bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) - { - return true; - } + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } - bool string(string_t& /*unused*/) - { - return true; - } + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } - bool binary(binary_t& /*unused*/) - { - return true; - } + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } - bool start_object(std::size_t /*unused*/ = static_cast(-1)) - { - return true; + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } } - bool key(string_t& /*unused*/) + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() { - return true; + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } } - bool end_object() + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept { - return true; + f = std::strtof(str, endptr); } - bool start_array(std::size_t /*unused*/ = static_cast(-1)) + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept { - return true; + f = std::strtod(str, endptr); } - bool end_array() + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept { - return true; + f = std::strtold(str, endptr); } - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 8259. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 8259. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated. { - return false; - } -}; + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + decimal_point_position = token_buffer.size() - 1; + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann -// SPDX-License-Identifier: MIT +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + default: + goto scan_number_done; + } -#include // array -#include // localeconv -#include // size_t -#include // snprintf -#include // strtof, strtod, strtold, strtoll, strtoull -#include // initializer_list -#include // char_traits, string -#include // move -#include // vector +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } -// #include + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } -// #include + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } -// #include +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } -// #include + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ + default: + goto scan_number_done; + } -/////////// -// lexer // -/////////// +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); -template -class lexer_base -{ - public: - /// token types for the parser - enum class token_type - { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value - value_integer, ///< a signed integer -- use get_number_integer() for actual value - value_float, ///< an floating point number -- use get_number_float() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input, ///< indicating the end of the input buffer - literal_or_value ///< a literal or the begin of a value (only for diagnostics) - }; + char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) + errno = 0; - /// return name of values of type token_type (only used for errors) - JSON_HEDLEY_RETURNS_NON_NULL - JSON_HEDLEY_CONST - static const char* token_type_name(const token_type t) noexcept - { - switch (t) + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) { - case token_type::uninitialized: - return ""; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case token_type::value_unsigned: - case token_type::value_integer: - case token_type::value_float: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return ""; - case token_type::end_of_input: - return "end of input"; - case token_type::literal_or_value: - return "'[', '{', or a literal"; - // LCOV_EXCL_START - default: // catch non-enum values - return "unknown token"; - // LCOV_EXCL_STOP + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno != ERANGE) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } } - } -}; -/*! -@brief lexical analysis + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); -This class organizes the lexical analysis during JSON deserialization. -*/ -template -class lexer : public lexer_base -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using char_type = typename InputAdapterType::char_type; - using char_int_type = typename char_traits::int_type; + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - public: - using token_type = typename lexer_base::token_type; + if (errno != ERANGE) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } - explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept - : ia(std::move(adapter)) - , ignore_comments(ignore_comments_) - , decimal_point_char(static_cast(get_decimal_point())) - {} + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); - // delete because of pointer members - lexer(const lexer&) = delete; - lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - lexer& operator=(lexer&) = delete; - lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) - ~lexer() = default; + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - private: - ///////////////////// - // locales - ///////////////////// + return token_type::value_float; + } - /// return the locale-dependent decimal point - JSON_HEDLEY_PURE - static char get_decimal_point() noexcept + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) { - const auto* loc = localeconv(); - JSON_ASSERT(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + JSON_ASSERT(char_traits::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_HEDLEY_UNLIKELY(char_traits::to_char_type(get()) != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; } ///////////////////// - // scan functions + // input management ///////////////////// - /*! - @brief get codepoint from 4 hex characters following `\u` + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + decimal_point_position = std::string::npos; + token_string.push_back(char_traits::to_char_type(current)); + } - For input "\u c1 c2 c3 c4" the codepoint is: - (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 - = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + /* + @brief get next character from the input - Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' - must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The - conversion is done by subtracting the offset (0x30, 0x37, and 0x57) - between the ASCII value of the character and the desired integer value. + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. - @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or - non-hex character) + @return character read from the input */ - int get_codepoint() + char_int_type get() { - // this function only makes sense after reading `\u` - JSON_ASSERT(current == 'u'); - int codepoint = 0; + ++position.chars_read_total; + ++position.chars_read_current_line; - const auto factors = { 12u, 8u, 4u, 0u }; - for (const auto factor : factors) + if (next_unget) { - get(); + // just reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); + } - if (current >= '0' && current <= '9') - { - codepoint += static_cast((static_cast(current) - 0x30u) << factor); - } - else if (current >= 'A' && current <= 'F') - { - codepoint += static_cast((static_cast(current) - 0x37u) << factor); - } - else if (current >= 'a' && current <= 'f') - { - codepoint += static_cast((static_cast(current) - 0x57u) << factor); - } - else + if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) + { + token_string.push_back(char_traits::to_char_type(current)); + } + + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } + + return current; + } + + /*! + @brief unget current character (read it again on next get) + + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. + */ + void unget() + { + next_unget = true; + + --position.chars_read_total; + + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) + { + if (position.lines_read > 0) { - return -1; + --position.lines_read; } } + else + { + --position.chars_read_current_line; + } - JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); - return codepoint; + if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } } - /*! - @brief check if the next byte(s) are inside a given range + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } - Adds the current byte and, for each passed range, reads a new byte and - checks if it is inside the range. If a violation was detected, set up an - error message and return false. Otherwise, return true. + public: + ///////////////////// + // value getters + ///////////////////// - @param[in] ranges list of integers; interpreted as list of pairs of - inclusive lower and upper bound, respectively + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } - @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, - 1, 2, or 3 pairs. This precondition is enforced by an assertion. + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } - @return true if and only if no range violation was detected - */ - bool next_byte_in_range(std::initializer_list ranges) + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept { - JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); - add(current); + return value_float; + } - for (auto range = ranges.begin(); range != ranges.end(); ++range) + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + // translate decimal points from locale back to '.' (#4084) + if (decimal_point_char != '.' && decimal_point_position != std::string::npos) { - get(); - if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions) + token_buffer[decimal_point_position] = '.'; + } + return token_buffer; + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast(c) <= '\x1F') { - add(current); + // escape control characters + std::array cs{{}}; + static_cast((std::snprintf)(cs.data(), cs.size(), "", static_cast(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + result += cs.data(); } else { - error_message = "invalid string: ill-formed UTF-8 byte"; - return false; + // add character as is + result.push_back(static_cast(c)); } } + return result; + } + + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } + + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); return true; } - /*! - @brief scan a string literal - - This function scans a string according to Sect. 7 of RFC 8259. While - scanning, bytes are escaped and copied into buffer token_buffer. Then the - function returns successfully, token_buffer is *not* null-terminated (as it - may contain \0 bytes), and token_buffer.size() is the number of bytes in the - string. - - @return token_type::value_string if string could be successfully scanned, - token_type::parse_error otherwise + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } - @note In case of errors, variable error_message contains a textual - description. - */ - token_type scan_string() + token_type scan() { - // reset token_buffer (ignore opening quote) - reset(); + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } - // we entered the function by reading an open quote - JSON_ASSERT(current == '\"'); + // read next character and ignore whitespace + skip_whitespace(); - while (true) + // ignore comments + while (ignore_comments && current == '/') { - // get next character - switch (get()) + if (!scan_comment()) { - // end of file while parsing string - case char_traits::eof(): - { - error_message = "invalid string: missing closing quote"; - return token_type::parse_error; - } - - // closing quote - case '\"': - { - return token_type::value_string; - } - - // escapes - case '\\': - { - switch (get()) - { - // quotation mark - case '\"': - add('\"'); - break; - // reverse solidus - case '\\': - add('\\'); - break; - // solidus - case '/': - add('/'); - break; - // backspace - case 'b': - add('\b'); - break; - // form feed - case 'f': - add('\f'); - break; - // line feed - case 'n': - add('\n'); - break; - // carriage return - case 'r': - add('\r'); - break; - // tab - case 't': - add('\t'); - break; - - // unicode escapes - case 'u': - { - const int codepoint1 = get_codepoint(); - int codepoint = codepoint1; // start with codepoint1 + return token_type::parse_error; + } - if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } + // skip following whitespace + skip_whitespace(); + } - // check if code point is a high surrogate - if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) - { - // expect next \uxxxx entry - if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) - { - const int codepoint2 = get_codepoint(); + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; - if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } + // literals + case 't': + { + std::array true_literal = {{static_cast('t'), static_cast('r'), static_cast('u'), static_cast('e')}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array false_literal = {{static_cast('f'), static_cast('a'), static_cast('l'), static_cast('s'), static_cast('e')}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array null_literal = {{static_cast('n'), static_cast('u'), static_cast('l'), static_cast('l')}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } - // check if codepoint2 is a low surrogate - if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) - { - // overwrite codepoint - codepoint = static_cast( - // high surrogate occupies the most significant 22 bits - (static_cast(codepoint1) << 10u) - // low surrogate occupies the least significant 15 bits - + static_cast(codepoint2) - // there is still the 0xD800, 0xDC00 and 0x10000 noise - // in the result, so we have to subtract with: - // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - - 0x35FDC00u); - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; - return token_type::parse_error; - } - } + // string + case '\"': + return scan_string(); - // result of the above calculation yields a proper codepoint - JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); - // translate codepoint into bytes - if (codepoint < 0x80) - { - // 1-byte characters: 0xxxxxxx (ASCII) - add(static_cast(codepoint)); - } - else if (codepoint <= 0x7FF) - { - // 2-byte characters: 110xxxxx 10xxxxxx - add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else if (codepoint <= 0xFFFF) - { - // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx - add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else - { - // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case char_traits::eof(): + return token_type::end_of_input; - break; - } + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } - // other characters after escape - default: - error_message = "invalid string: forbidden character after backslash"; - return token_type::parse_error; - } + private: + /// input adapter + InputAdapterType ia; - break; - } + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; - // invalid control characters - case 0x00: - { - error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; - return token_type::parse_error; - } + /// the current character + char_int_type current = char_traits::eof(); - case 0x01: - { - error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; - return token_type::parse_error; - } + /// whether the next get() call should just return current + bool next_unget = false; - case 0x02: - { - error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; - return token_type::parse_error; - } + /// the start position of the current token + position_t position {}; - case 0x03: - { - error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; - return token_type::parse_error; - } + /// raw input token string (for error messages) + std::vector token_string {}; - case 0x04: - { - error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; - return token_type::parse_error; - } + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; - case 0x05: - { - error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; - return token_type::parse_error; - } + /// a description of occurred lexer errors + const char* error_message = ""; - case 0x06: - { - error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; - return token_type::parse_error; - } + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; - case 0x07: - { - error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; - return token_type::parse_error; - } + /// the decimal point + const char_int_type decimal_point_char = '.'; + /// the position of the decimal point in the input + std::size_t decimal_point_position = std::string::npos; +}; - case 0x08: - { - error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; - return token_type::parse_error; - } +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END - case 0x09: - { - error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; - return token_type::parse_error; - } +// #include - case 0x0A: - { - error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; - return token_type::parse_error; - } +// #include - case 0x0B: - { - error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; - return token_type::parse_error; - } +NLOHMANN_JSON_NAMESPACE_BEGIN - case 0x0C: - { - error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; - return token_type::parse_error; - } +/*! +@brief SAX interface - case 0x0D: - { - error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; - return token_type::parse_error; - } +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; - case 0x0E: - { - error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; - return token_type::parse_error; - } + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; - case 0x0F: - { - error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; - return token_type::parse_error; - } + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; - case 0x10: - { - error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; - return token_type::parse_error; - } + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; - case 0x11: - { - error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; - return token_type::parse_error; - } + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; - case 0x12: - { - error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; - return token_type::parse_error; - } + /*! + @brief a floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; - case 0x13: - { - error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; - return token_type::parse_error; - } + /*! + @brief a string value was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string value. + */ + virtual bool string(string_t& val) = 0; - case 0x14: - { - error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; - return token_type::parse_error; - } + /*! + @brief a binary value was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary value. + */ + virtual bool binary(binary_t& val) = 0; - case 0x15: - { - error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; - return token_type::parse_error; - } + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; - case 0x16: - { - error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; - return token_type::parse_error; - } + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; - case 0x17: - { - error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; - return token_type::parse_error; - } + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; - case 0x18: - { - error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; - return token_type::parse_error; - } + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; - case 0x19: - { - error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; - return token_type::parse_error; - } + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; - case 0x1A: - { - error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; - return token_type::parse_error; - } + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; - case 0x1B: - { - error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; - return token_type::parse_error; - } +namespace detail +{ +constexpr std::size_t unknown_size() +{ + return (std::numeric_limits::max)(); +} - case 0x1C: - { - error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; - return token_type::parse_error; - } +/*! +@brief SAX implementation to create a JSON value from SAX events - case 0x1D: - { - error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; - return token_type::parse_error; - } +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. - case 0x1E: - { - error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; - return token_type::parse_error; - } +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. - case 0x1F: - { - error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; - return token_type::parse_error; - } +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using lexer_t = lexer; - // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) - case 0x20: - case 0x21: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - { - add(current); - break; - } + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr) + : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + {} - // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xC2: - case 0xC3: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD5: - case 0xD6: - case 0xD7: - case 0xD8: - case 0xD9: - case 0xDA: - case 0xDB: - case 0xDC: - case 0xDD: - case 0xDE: - case 0xDF: - { - if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) - { - return token_type::parse_error; - } - break; - } + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; - // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xE0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool null() + { + handle_value(nullptr); + return true; + } - // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF - // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xEE: - case 0xEF: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } - // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xED: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } - // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xF0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } - // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xF1: - case 0xF2: - case 0xF3: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool string(string_t& val) + { + handle_value(val); + return true; + } - // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xF4: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } - // remaining bytes (80..C1 and F5..FF) are ill-formed - default: - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; - } - } + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); + } + + return true; } - /*! - * @brief scan a comment - * @return whether comment could be scanned successfully - */ - bool scan_comment() + bool key(string_t& val) { - switch (get()) - { - // single-line comments skip input until a newline or EOF is read - case '/': - { - while (true) - { - switch (get()) - { - case '\n': - case '\r': - case char_traits::eof(): - case '\0': - return true; + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); - default: - break; - } - } - } + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val)); + return true; + } - // multi-line comments skip input until */ is read - case '*': - { - while (true) - { - switch (get()) - { - case char_traits::eof(): - case '\0': - { - error_message = "invalid comment; missing closing '*/'"; - return false; - } + bool end_object() + { + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_object()); - case '*': - { - switch (get()) - { - case '/': - return true; +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif - default: - { - unget(); - continue; - } - } - } + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } - default: - continue; - } - } - } + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); - // unexpected character after reading '/' - default: - { - error_message = "invalid comment; expecting '/' or '*' after '/'"; - return false; - } +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif + + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); } + + return true; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(float& f, const char* str, char** endptr) noexcept + bool end_array() { - f = std::strtof(str, endptr); + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(ref_stack.back()->is_array()); + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(double& f, const char* str, char** endptr) noexcept + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) { - f = std::strtod(str, endptr); + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; } - JSON_HEDLEY_NON_NULL(2) - static void strtof(long double& f, const char* str, char** endptr) noexcept + constexpr bool is_errored() const { - f = std::strtold(str, endptr); + return errored; } - /*! - @brief scan a number literal + private: - This function scans a string according to Sect. 6 of RFC 8259. +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); - The function is realized with a deterministic finite state machine derived - from the grammar described in RFC 8259. Starting in state "init", the - input is read and used to determined the next state. Only state "done" - accepts the number. State "error" is a trap state to model errors. In the - table below, "anything" means any character but the ones listed before. + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } + + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } + + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } + + // As we handle the start and end positions for values created during parsing, + // we do not expect the following value type to be called. Regardless, set the positions + // in case this is created manually or through a different constructor. Exclude from lcov + // since the exact condition of this switch is esoteric. + // LCOV_EXCL_START + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } + // LCOV_EXCL_STOP + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } + } + } +#endif + + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); - state | 0 | 1-9 | e E | + | - | . | anything - ---------|----------|----------|----------|---------|---------|----------|----------- - init | zero | any1 | [error] | [error] | minus | [error] | [error] - minus | zero | any1 | [error] | [error] | [error] | [error] | [error] - zero | done | done | exponent | done | done | decimal1 | done - any1 | any1 | any1 | exponent | done | done | decimal1 | done - decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] - decimal2 | decimal2 | decimal2 | exponent | done | done | done | done - exponent | any2 | any2 | [error] | sign | sign | [error] | [error] - sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] - any2 | any2 | any2 | done | done | done | done | done +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(root); +#endif - The state machine is realized with one label per state (prefixed with - "scan_number_") and `goto` statements between them. The state machine - contains cycles, but any cycle can be left when EOF is read. Therefore, - the function is guaranteed to terminate. + return &root; + } - During scanning, the read bytes are stored in token_buffer. This string is - then converted to a signed integer, an unsigned integer, or a - floating-point number. + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - @return token_type::value_unsigned, token_type::value_integer, or - token_type::value_float if number could be successfully scanned, - token_type::parse_error otherwise + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::forward(v)); - @note The scanner is independent of the current locale. Internally, the - locale's decimal point is used instead of `.` to work with the - locale-dependent converters. - */ - token_type scan_number() // lgtm [cpp/use-of-goto] - { - // reset token_buffer to store the number's bytes - reset(); +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back()); +#endif - // the type of the parsed number; initially set to unsigned; will be - // changed if minus sign, decimal point or exponent is read - token_type number_type = token_type::value_unsigned; + return &(ref_stack.back()->m_data.m_value.array->back()); + } - // state (init): we just found out we need to scan a number - switch (current) - { - case '-': - { - add(current); - goto scan_number_minus; - } + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); - case '0': - { - add(current); - goto scan_number_zero; - } +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(*object_element); +#endif - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } + return object_element; + } - // all other characters are rejected outside scan_number() - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE - } + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; -scan_number_minus: - // state: we just parsed a leading minus sign - number_type = token_type::value_integer; - switch (get()) - { - case '0': - { - add(current); - goto scan_number_zero; - } +template +class json_sax_dom_callback_parser +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + using lexer_t = lexer; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } + json_sax_dom_callback_parser(BasicJsonType& r, + parser_callback_t cb, + const bool allow_exceptions_ = true, + lexer_t* lexer_ = nullptr) + : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_) + { + keep_stack.push_back(true); + } - default: - { - error_message = "invalid number; expected digit after '-'"; - return token_type::parse_error; - } - } + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; -scan_number_zero: - // state: we just parse a zero (maybe with a leading minus sign) - switch (get()) - { - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } + bool null() + { + handle_value(nullptr); + return true; + } - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } + bool boolean(bool val) + { + handle_value(val); + return true; + } - default: - goto scan_number_done; - } + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } -scan_number_any1: - // state: we just parsed a number 0-9 (maybe with a leading minus sign) - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } + bool string(string_t& val) + { + handle_value(val); + return true; + } - default: - goto scan_number_done; - } + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } -scan_number_decimal1: - // state: we just parsed a decimal point - number_type = token_type::value_float; - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); - default: - { - error_message = "invalid number; expected digit after '.'"; - return token_type::parse_error; - } - } + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); -scan_number_decimal2: - // we just parsed at least one number after a decimal point - switch (get()) + if (ref_stack.back()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the object here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) { - add(current); - goto scan_number_decimal2; + // Lexer has read the first character of the object, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; } +#endif - case 'e': - case 'E': + // check object limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) { - add(current); - goto scan_number_exponent; + JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back())); } - - default: - goto scan_number_done; } + return true; + } -scan_number_exponent: - // we just parsed an exponent - number_type = token_type::value_float; - switch (get()) - { - case '+': - case '-': - { - add(current); - goto scan_number_sign; - } + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); - default: - { - error_message = - "invalid number; expected '+', '-', or digit after exponent"; - return token_type::parse_error; - } + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded); } -scan_number_sign: - // we just parsed an exponent sign - switch (get()) + return true; + } + + bool end_object() + { + if (ref_stack.back()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) { - add(current); - goto scan_number_any2; - } + // discard object + *ref_stack.back() = discarded; - default: +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded object. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif + } + else { - error_message = "invalid number; expected digit after exponent sign"; - return token_type::parse_error; + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) + { + // Lexer's position is past the closing brace, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); + } +#endif + + ref_stack.back()->set_parents(); } } -scan_number_any2: - // we just parsed a number after the exponent or exponent sign - switch (get()) + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) { - add(current); - goto scan_number_any2; + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } } - - default: - goto scan_number_done; } -scan_number_done: - // unget the character after the number (we only read it to know that - // we are done scanning a number) - unget(); + return true; + } - char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - errno = 0; + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); - // try to parse integers first and fall back to floats - if (number_type == token_type::value_unsigned) + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + if (ref_stack.back()) { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); +#if JSON_DIAGNOSTIC_POSITIONS + // Manually set the start position of the array here. + // Ensure this is after the call to handle_value to ensure correct start position. + if (m_lexer_ref) + { + // Lexer has read the first character of the array, so + // subtract 1 from the position to get the correct start position. + ref_stack.back()->start_position = m_lexer_ref->get_position() - 1; + } +#endif - if (errno == 0) + // check array limit + if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size())) { - value_unsigned = static_cast(x); - if (value_unsigned == x) - { - return token_type::value_unsigned; - } + JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back())); } } - else if (number_type == token_type::value_integer) - { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + return true; + } + + bool end_array() + { + bool keep = true; - if (errno == 0) + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) { - value_integer = static_cast(x); - if (value_integer == x) + +#if JSON_DIAGNOSTIC_POSITIONS + if (m_lexer_ref) { - return token_type::value_integer; + // Lexer's position is past the closing bracket, so set that as the end position. + ref_stack.back()->end_position = m_lexer_ref->get_position(); } +#endif + + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; + +#if JSON_DIAGNOSTIC_POSITIONS + // Set start/end positions for discarded array. + handle_diagnostic_positions_for_json_value(*ref_stack.back()); +#endif } } - // this code is reached if we parse a floating-point number or if an - // integer conversion above failed - strtof(value_float, token_buffer.data(), &endptr); + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->pop_back(); + } - return token_type::value_float; + return true; } - /*! - @param[in] literal_text the literal text to expect - @param[in] length the length of the passed literal text - @param[in] return_type the token type to return on success - */ - JSON_HEDLEY_NON_NULL(2) - token_type scan_literal(const char_type* literal_text, const std::size_t length, - token_type return_type) + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) { - JSON_ASSERT(char_traits::to_char_type(current) == literal_text[0]); - for (std::size_t i = 1; i < length; ++i) + errored = true; + static_cast(ex); + if (allow_exceptions) { - if (JSON_HEDLEY_UNLIKELY(char_traits::to_char_type(get()) != literal_text[i])) - { - error_message = "invalid literal"; - return token_type::parse_error; - } + JSON_THROW(ex); } - return return_type; + return false; } - ///////////////////// - // input management - ///////////////////// - - /// reset token_buffer; current character is beginning of token - void reset() noexcept + constexpr bool is_errored() const { - token_buffer.clear(); - token_string.clear(); - token_string.push_back(char_traits::to_char_type(current)); + return errored; } - /* - @brief get next character from the input + private: + +#if JSON_DIAGNOSTIC_POSITIONS + void handle_diagnostic_positions_for_json_value(BasicJsonType& v) + { + if (m_lexer_ref) + { + // Lexer has read past the current field value, so set the end position to the current position. + // The start position will be set below based on the length of the string representation + // of the value. + v.end_position = m_lexer_ref->get_position(); + + switch (v.type()) + { + case value_t::boolean: + { + // 4 and 5 are the string length of "true" and "false" + v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5); + break; + } - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a - `char_traits::eof()` in that case. Stores the scanned characters - for use in error messages. + case value_t::null: + { + // 4 is the string length of "null" + v.start_position = v.end_position - 4; + break; + } - @return character read from the input - */ - char_int_type get() - { - ++position.chars_read_total; - ++position.chars_read_current_line; + case value_t::string: + { + // include the length of the quotes, which is 2 + v.start_position = v.end_position - v.m_data.m_value.string->size() - 2; + break; + } - if (next_unget) - { - // just reset the next_unget variable and work with current - next_unget = false; - } - else - { - current = ia.get_character(); - } + case value_t::discarded: + { + v.end_position = std::string::npos; + v.start_position = v.end_position; + break; + } - if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) - { - token_string.push_back(char_traits::to_char_type(current)); - } + case value_t::binary: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + { + v.start_position = v.end_position - m_lexer_ref->get_string().size(); + break; + } - if (current == '\n') - { - ++position.lines_read; - position.chars_read_current_line = 0; + case value_t::object: + case value_t::array: + { + // object and array are handled in start_object() and start_array() handlers + // skip setting the values here. + break; + } + default: // LCOV_EXCL_LINE + // Handle all possible types discretely, default handler should never be reached. + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE + } } - - return current; } +#endif /*! - @brief unget current character (read it again on next get) + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. - We implement unget by setting variable next_unget to true. The input is not - changed - we just simulate ungetting by modifying chars_read_total, - chars_read_current_line, and token_string. The next call to get() will - behave as if the unget character is read again. + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) */ - void unget() + template + std::pair handle_value(Value&& v, const bool skip_callback = false) { - next_unget = true; - - --position.chars_read_total; + JSON_ASSERT(!keep_stack.empty()); - // in case we "unget" a newline, we have to also decrement the lines_read - if (position.chars_read_current_line == 0) - { - if (position.lines_read > 0) - { - --position.lines_read; - } - } - else + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) { - --position.chars_read_current_line; + return {false, nullptr}; } - if (JSON_HEDLEY_LIKELY(current != char_traits::eof())) - { - JSON_ASSERT(!token_string.empty()); - token_string.pop_back(); - } - } + // create value + auto value = BasicJsonType(std::forward(v)); - /// add a character to token_buffer - void add(char_int_type c) - { - token_buffer.push_back(static_cast(c)); - } +#if JSON_DIAGNOSTIC_POSITIONS + handle_diagnostic_positions_for_json_value(value); +#endif - public: - ///////////////////// - // value getters - ///////////////////// + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); - /// return integer value - constexpr number_integer_t get_number_integer() const noexcept - { - return value_integer; - } + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } - /// return unsigned integer value - constexpr number_unsigned_t get_number_unsigned() const noexcept - { - return value_unsigned; - } + if (ref_stack.empty()) + { + root = std::move(value); + return {true, & root}; + } - /// return floating-point value - constexpr number_float_t get_number_float() const noexcept - { - return value_float; - } + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } - /// return current string value (implicitly resets the token; useful only once) - string_t& get_string() - { - return token_buffer; - } + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - ///////////////////// - // diagnostics - ///////////////////// + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value)); + return {true, & (ref_stack.back()->m_data.m_value.array->back())}; + } - /// return position of last read token - constexpr position_t get_position() const noexcept - { - return position; - } + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); - /// return the last read token (for errors only). Will never contain EOF - /// (an arbitrary value that is not a valid char value, often -1), because - /// 255 may legitimately occur. May contain NUL, which should be escaped. - std::string get_token_string() const - { - // escape control characters - std::string result; - for (const auto c : token_string) + if (!store_element) { - if (static_cast(c) <= '\x1F') - { - // escape control characters - std::array cs{{}}; - static_cast((std::snprintf)(cs.data(), cs.size(), "", static_cast(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) - result += cs.data(); - } - else - { - // add character as is - result.push_back(static_cast(c)); - } + return {false, nullptr}; } - return result; + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; } - /// return syntax error message - JSON_HEDLEY_RETURNS_NON_NULL - constexpr const char* get_error_message() const noexcept - { - return error_message; - } + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; // NOLINT(readability-redundant-member-init) + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; // NOLINT(readability-redundant-member-init) + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; + /// the lexer reference to obtain the current position + lexer_t* m_lexer_ref = nullptr; +}; - ///////////////////// - // actual scanner - ///////////////////// +template +class json_sax_acceptor +{ + public: + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; - /*! - @brief skip the UTF-8 byte order mark - @return true iff there is no BOM or the correct BOM has been skipped - */ - bool skip_bom() + bool null() { - if (get() == 0xEF) - { - // check if we completely parse the BOM - return get() == 0xBB && get() == 0xBF; - } + return true; + } - // the first character is not the beginning of the BOM; unget it to - // process is later - unget(); + bool boolean(bool /*unused*/) + { return true; } - void skip_whitespace() + bool number_integer(number_integer_t /*unused*/) { - do - { - get(); - } - while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + return true; } - token_type scan() + bool number_unsigned(number_unsigned_t /*unused*/) { - // initially, skip the BOM - if (position.chars_read_total == 0 && !skip_bom()) - { - error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; - return token_type::parse_error; - } - - // read next character and ignore whitespace - skip_whitespace(); - - // ignore comments - while (ignore_comments && current == '/') - { - if (!scan_comment()) - { - return token_type::parse_error; - } - - // skip following whitespace - skip_whitespace(); - } - - switch (current) - { - // structural characters - case '[': - return token_type::begin_array; - case ']': - return token_type::end_array; - case '{': - return token_type::begin_object; - case '}': - return token_type::end_object; - case ':': - return token_type::name_separator; - case ',': - return token_type::value_separator; - - // literals - case 't': - { - std::array true_literal = {{static_cast('t'), static_cast('r'), static_cast('u'), static_cast('e')}}; - return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); - } - case 'f': - { - std::array false_literal = {{static_cast('f'), static_cast('a'), static_cast('l'), static_cast('s'), static_cast('e')}}; - return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); - } - case 'n': - { - std::array null_literal = {{static_cast('n'), static_cast('u'), static_cast('l'), static_cast('l')}}; - return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); - } - - // string - case '\"': - return scan_string(); - - // number - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return scan_number(); - - // end of input (the null byte is needed when parsing from - // string literals) - case '\0': - case char_traits::eof(): - return token_type::end_of_input; - - // error - default: - error_message = "invalid literal"; - return token_type::parse_error; - } + return true; } - private: - /// input adapter - InputAdapterType ia; - - /// whether comments should be ignored (true) or signaled as errors (false) - const bool ignore_comments = false; + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } - /// the current character - char_int_type current = char_traits::eof(); + bool string(string_t& /*unused*/) + { + return true; + } - /// whether the next get() call should just return current - bool next_unget = false; + bool binary(binary_t& /*unused*/) + { + return true; + } - /// the start position of the current token - position_t position {}; + bool start_object(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } - /// raw input token string (for error messages) - std::vector token_string {}; + bool key(string_t& /*unused*/) + { + return true; + } - /// buffer for variable-length tokens (numbers, strings) - string_t token_buffer {}; + bool end_object() + { + return true; + } - /// a description of occurred lexer errors - const char* error_message = ""; + bool start_array(std::size_t /*unused*/ = detail::unknown_size()) + { + return true; + } - // number values - number_integer_t value_integer = 0; - number_unsigned_t value_unsigned = 0; - number_float_t value_float = 0; + bool end_array() + { + return true; + } - /// the decimal point - const char_int_type decimal_point_char = '.'; + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } }; } // namespace detail NLOHMANN_JSON_NAMESPACE_END +// #include + // #include // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -9207,7 +9841,7 @@ static inline bool little_endianness(int num = 1) noexcept /*! @brief deserialization of CBOR, MessagePack, and UBJSON values */ -template> +template> class binary_reader { using number_integer_t = typename BasicJsonType::number_integer_t; @@ -9314,7 +9948,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -9470,6 +10104,12 @@ class binary_reader return get_number(input_format_t::bson, value) && sax->number_integer(value); } + case 0x11: // uint64 + { + std::uint64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_unsigned(value); + } + default: // anything else not supported (yet) { std::array cr{{}}; @@ -9536,7 +10176,7 @@ class binary_reader std::int32_t document_size{}; get_number(input_format_t::bson, document_size); - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -9796,7 +10436,7 @@ class binary_reader } case 0x9F: // array (indefinite length) - return get_cbor_array(static_cast(-1), tag_handler); + return get_cbor_array(detail::unknown_size(), tag_handler); // map (0x00..0x17 pairs of data items follow) case 0xA0: @@ -9850,7 +10490,7 @@ class binary_reader } case 0xBF: // map (indefinite length) - return get_cbor_object(static_cast(-1), tag_handler); + return get_cbor_object(detail::unknown_size(), tag_handler); case 0xC6: // tagged item case 0xC7: @@ -10238,7 +10878,7 @@ class binary_reader } /*! - @param[in] len the length of the array or static_cast(-1) for an + @param[in] len the length of the array or detail::unknown_size() for an array of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether array creation completed @@ -10251,7 +10891,7 @@ class binary_reader return false; } - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -10276,7 +10916,7 @@ class binary_reader } /*! - @param[in] len the length of the object or static_cast(-1) for an + @param[in] len the length of the object or detail::unknown_size() for an object of indefinite size @param[in] tag_handler how CBOR tags should be treated @return whether object creation completed @@ -10292,7 +10932,7 @@ class binary_reader if (len != 0) { string_t key; - if (len != static_cast(-1)) + if (len != detail::unknown_size()) { for (std::size_t i = 0; i < len; ++i) { @@ -11455,6 +12095,16 @@ class binary_reader case 'Z': // null return sax->null(); + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + case 'U': { std::uint8_t number{}; @@ -11655,7 +12305,7 @@ class binary_reader return false; } - if (size_and_type.second == 'C') + if (size_and_type.second == 'C' || size_and_type.second == 'B') { size_and_type.second = 'U'; } @@ -11677,6 +12327,13 @@ class binary_reader return (sax->end_array() && sax->end_object()); } + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + if (size_and_type.first != npos) { if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) @@ -11710,7 +12367,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -11788,7 +12445,7 @@ class binary_reader } else { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -11899,6 +12556,29 @@ class binary_reader return current = ia.get_character(); } + /*! + @brief get_to read into a primitive type + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns false instead + + @return bool, whether the read was successful + */ + template + bool get_to(T& dest, const input_format_t format, const char* context) + { + auto new_chars_read = ia.get_elements(&dest); + chars_read += new_chars_read; + if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) + { + // in case of failure, advance position by 1 to report failing location + ++chars_read; + sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + return false; + } + return true; + } + /*! @return character read from the input after ignoring all 'N' entries */ @@ -11913,6 +12593,28 @@ class binary_reader return current; } + template + static void byte_swap(NumberType& number) + { + constexpr std::size_t sz = sizeof(number); +#ifdef __cpp_lib_byteswap + if constexpr (sz == 1) + { + return; + } + if constexpr(std::is_integral_v) + { + number = std::byteswap(number); + return; + } +#endif + auto* ptr = reinterpret_cast(&number); + for (std::size_t i = 0; i < sz / 2; ++i) + { + std::swap(ptr[i], ptr[sz - i - 1]); + } + } + /* @brief read a number from the input @@ -11931,29 +12633,16 @@ class binary_reader template bool get_number(const input_format_t format, NumberType& result) { - // step 1: read input into array with system's byte order - std::array vec{}; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) - { - return false; - } + // read in the original format - // reverse byte order prior to conversion if necessary - if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) - { - vec[sizeof(NumberType) - i - 1] = static_cast(current); - } - else - { - vec[i] = static_cast(current); // LCOV_EXCL_LINE - } + if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number"))) + { + return false; + } + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + byte_swap(result); } - - // step 2: convert array into number of type T and return - std::memcpy(&result, vec.data(), sizeof(NumberType)); return true; } @@ -12092,7 +12781,7 @@ class binary_reader } private: - static JSON_INLINE_VARIABLE constexpr std::size_t npos = static_cast(-1); + static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size(); /// input adapter InputAdapterType ia; @@ -12118,6 +12807,7 @@ class binary_reader #define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ make_array( \ + bjd_type{'B', "byte"}, \ bjd_type{'C', "char"}, \ bjd_type{'D', "double"}, \ bjd_type{'I', "int16"}, \ @@ -12160,10 +12850,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12237,10 +12927,10 @@ class parser public: /// a parser reading from an input adapter explicit parser(InputAdapterType&& adapter, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions_ = true, const bool skip_comments = false) - : callback(cb) + : callback(std::move(cb)) , m_lexer(std::move(adapter), skip_comments) , allow_exceptions(allow_exceptions_) { @@ -12262,7 +12952,7 @@ class parser { if (callback) { - json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12290,7 +12980,7 @@ class parser } else { - json_sax_dom_parser sdp(result, allow_exceptions); + json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); sax_parse_internal(&sdp); // in strict mode, input must be completely read @@ -12362,7 +13052,7 @@ class parser { case token_type::begin_object: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { return false; } @@ -12407,7 +13097,7 @@ class parser case token_type::begin_array: { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(static_cast(-1)))) + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { return false; } @@ -12689,10 +13379,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12702,10 +13392,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -12861,10 +13551,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13331,7 +14021,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator==(const IterImpl& other) const @@ -13342,7 +14032,11 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + return true; + } switch (m_object->m_data.m_type) { @@ -13367,7 +14061,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > bool operator!=(const IterImpl& other) const @@ -13377,7 +14071,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<(const iter_impl& other) const { @@ -13387,7 +14081,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); } - JSON_ASSERT(m_object != nullptr); + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false + return false; + } switch (m_object->m_data.m_type) { @@ -13412,7 +14111,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator<=(const iter_impl& other) const { @@ -13421,7 +14120,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. */ bool operator>(const iter_impl& other) const { @@ -13430,7 +14129,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci /*! @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. + @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized. */ bool operator>=(const iter_impl& other) const { @@ -13623,10 +14322,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13758,10 +14457,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -13800,10 +14499,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14033,7 +14732,7 @@ class json_pointer } const char* p = s.c_str(); - char* p_end = nullptr; + char* p_end = nullptr; // NOLINT(misc-const-correctness) errno = 0; // strtoull doesn't reset errno const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) if (p == p_end // invalid input or empty string @@ -14555,7 +15254,7 @@ class json_pointer // iterate array and use index as reference string for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) { - flatten(detail::concat(reference_string, '/', std::to_string(i)), + flatten(detail::concat(reference_string, '/', std::to_string(i)), value.m_data.m_value.array->operator[](i), result); } } @@ -14574,7 +15273,7 @@ class json_pointer // iterate object and use keys as reference string for (const auto& element : *value.m_data.m_value.object) { - flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); } } break; @@ -14795,10 +15494,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14880,6 +15579,8 @@ NLOHMANN_JSON_NAMESPACE_END // #include +// #include + // #include // #include @@ -14887,10 +15588,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -14913,10 +15614,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -15067,6 +15768,13 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + /////////////////// // binary writer // /////////////////// @@ -15651,7 +16359,7 @@ class binary_writer case value_t::binary: { // step 0: determine if the binary type has a set subtype to - // determine whether or not to use the ext or fixext types + // determine whether to use the ext or fixext types const bool use_ext = j.m_data.m_value.binary->has_subtype(); // step 1: write control byte and the byte string length @@ -15774,11 +16482,14 @@ class binary_writer @param[in] use_type whether to use '$' prefixes (optimized format) @param[in] add_prefix whether prefixes need to be used for this value @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 */ void write_ubjson(const BasicJsonType& j, const bool use_count, const bool use_type, const bool add_prefix = true, - const bool use_bjdata = false) + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) { + const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3; + switch (j.type()) { case value_t::null: @@ -15868,7 +16579,7 @@ class binary_writer for (const auto& el : *j.m_data.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -15886,11 +16597,11 @@ class binary_writer oa->write_character(to_char_type('[')); } - if (use_type && !j.m_data.m_value.binary->empty()) + if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) { JSON_ASSERT(use_count); oa->write_character(to_char_type('$')); - oa->write_character('U'); + oa->write_character(bjdata_draft3 ? 'B' : 'U'); } if (use_count) @@ -15909,7 +16620,7 @@ class binary_writer { for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) { - oa->write_character(to_char_type('U')); + oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U')); oa->write_character(j.m_data.m_value.binary->data()[i]); } } @@ -15926,7 +16637,7 @@ class binary_writer { if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) { - if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) { break; } @@ -15970,7 +16681,7 @@ class binary_writer oa->write_characters( reinterpret_cast(el.first.c_str()), el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); } if (!use_count) @@ -16126,7 +16837,8 @@ class binary_writer } else { - JSON_THROW(out_of_range::create(407, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BSON as it does not fit int64"), &j)); + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); } } @@ -16654,10 +17366,11 @@ class binary_writer /*! @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid */ - bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type) + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) { std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, - {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, {"char", 'C'} + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} }; string_t key = "_ArrayType_"; @@ -16687,10 +17400,10 @@ class binary_writer oa->write_character('#'); key = "_ArraySize_"; - write_ubjson(value.at(key), use_count, use_type, true, true); + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); key = "_ArrayData_"; - if (dtype == 'U' || dtype == 'C') + if (dtype == 'U' || dtype == 'C' || dtype == 'B') { for (const auto& el : value.at(key)) { @@ -16881,11 +17594,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2008-2009 Björn Hoehrmann -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2008 - 2009 Björn Hoehrmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -16906,11 +17619,11 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2009 Florian Loitsch -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -17146,10 +17859,10 @@ boundaries compute_boundaries(FloatType value) // v- m- v m+ v+ const bool lower_boundary_is_closer = F == 0 && E > 1; - const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1); const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f - 1, v.e - 2) // (B) - : diyfp(2 * v.f - 1, v.e - 1); // (A) + ? diyfp((4 * v.f) - 1, v.e - 2) // (B) + : diyfp((2 * v.f) - 1, v.e - 1); // (A) // Determine the normalized w+ = m+. const diyfp w_plus = diyfp::normalize(m_plus); @@ -17379,7 +18092,7 @@ inline cached_power get_cached_power_for_binary_exponent(int e) JSON_ASSERT(e >= -1500); JSON_ASSERT(e <= 1500); const int f = kAlpha - e - 1; - const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + const int k = ((f * 78913) / (1 << 18)) + static_cast(f > 0); const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; JSON_ASSERT(index >= 0); @@ -17857,15 +18570,15 @@ inline char* append_exponent(char* buf, int e) } else if (k < 100) { - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } else { - *buf++ = static_cast('0' + k / 100); + *buf++ = static_cast('0' + (k / 100)); k %= 100; - *buf++ = static_cast('0' + k / 10); + *buf++ = static_cast('0' + (k / 10)); k %= 10; *buf++ = static_cast('0' + k); } @@ -18651,7 +19364,7 @@ class serializer @param[in] x unsigned integer number to count its digits @return number of decimal digits */ - inline unsigned int count_digits(number_unsigned_t x) noexcept + unsigned int count_digits(number_unsigned_t x) noexcept { unsigned int n_digits = 1; for (;;) @@ -18934,7 +19647,7 @@ class serializer ? (byte & 0x3fu) | (codep << 6u) : (0xFFu >> type) & (byte); - const std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); JSON_ASSERT(index < utf8d.size()); state = utf8d[index]; return state; @@ -18960,7 +19673,7 @@ class serializer * absolute values of INT_MIN and INT_MAX are usually not the same. See * #1708 for details. */ - inline number_unsigned_t remove_sign(number_integer_t x) noexcept + number_unsigned_t remove_sign(number_integer_t x) noexcept { JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) return static_cast(-(x + 1)) + 1; @@ -19002,10 +19715,10 @@ NLOHMANN_JSON_NAMESPACE_END // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -19030,7 +19743,7 @@ NLOHMANN_JSON_NAMESPACE_BEGIN /// for use within nlohmann::basic_json template , class Allocator = std::allocator>> - struct ordered_map : std::vector, Allocator> + struct ordered_map : std::vector, Allocator> { using key_type = Key; using mapped_type = T; @@ -19345,7 +20058,7 @@ template , template using require_input_iter = typename std::enable_if::iterator_category, - std::input_iterator_tag>::value>::type; + std::input_iterator_tag>::value>::type; template> void insert(InputIt first, InputIt last) @@ -19416,9 +20129,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec friend class ::nlohmann::detail::binary_writer; template friend class ::nlohmann::detail::binary_reader; - template + template friend class ::nlohmann::detail::json_sax_dom_parser; - template + template friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::exception; @@ -19439,7 +20152,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec ) { return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments); + std::move(cb), allow_exceptions, ignore_comments); } private: @@ -19472,6 +20185,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using error_handler_t = detail::error_handler_t; /// how to treat CBOR tags using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; /// helper type for initializer lists of basic_json values using initializer_list_t = std::initializer_list>; @@ -19551,7 +20266,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { basic_json result; - result["copyright"] = "(C) 2013-2023 Niels Lohmann"; + result["copyright"] = "(C) 2013-2025 Niels Lohmann"; result["name"] = "JSON for Modern C++"; result["url"] = "https://github.com/nlohmann/json"; result["version"]["string"] = @@ -19816,7 +20531,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec object = nullptr; // silence warning, see #821 if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3", nullptr)); // LCOV_EXCL_LINE + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE } break; } @@ -20052,10 +20767,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return it; } - reference set_parent(reference j, std::size_t old_capacity = static_cast(-1)) + reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size()) { #if JSON_DIAGNOSTICS - if (old_capacity != static_cast(-1)) + if (old_capacity != detail::unknown_size()) { // see https://github.com/nlohmann/json/issues/2838 JSON_ASSERT(type() == value_t::array); @@ -20135,8 +20850,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) - JSONSerializer::to_json(std::declval(), - std::forward(val)))) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) { JSONSerializer::to_json(*this, std::forward(val)); set_parents(); @@ -20149,6 +20864,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > basic_json(const BasicJsonType& val) +#if JSON_DIAGNOSTIC_POSITIONS + : start_position(val.start_pos()), + end_position(val.end_pos()) +#endif { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -20195,6 +20914,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE } JSON_ASSERT(m_data.m_type == val.type()); + set_parents(); assert_invariant(); } @@ -20331,7 +21051,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class InputIT, typename std::enable_if < std::is_same::value || std::is_same::value, int >::type = 0 > - basic_json(InputIT first, InputIT last) + basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param) { JSON_ASSERT(first.m_object != nullptr); JSON_ASSERT(last.m_object != nullptr); @@ -20446,6 +21166,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(const basic_json& other) : json_base_class_t(other) +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) + , end_position(other.end_position) +#endif { m_data.m_type = other.m_data.m_type; // check of passed value is valid @@ -20515,15 +21239,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ basic_json(basic_json&& other) noexcept : json_base_class_t(std::forward(other)), - m_data(std::move(other.m_data)) + m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check + , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check +#endif { // check that passed value is valid - other.assert_invariant(false); + other.assert_invariant(false); // cppcheck-suppress[accessForwarded] // invalidate payload other.m_data.m_type = value_t::null; other.m_data.m_value = {}; +#if JSON_DIAGNOSTIC_POSITIONS + other.start_position = std::string::npos; + other.end_position = std::string::npos; +#endif + set_parents(); assert_invariant(); } @@ -20544,6 +21277,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec using std::swap; swap(m_data.m_type, other.m_data.m_type); swap(m_data.m_value, other.m_data.m_value); + +#if JSON_DIAGNOSTIC_POSITIONS + swap(start_position, other.start_position); + swap(end_position, other.end_position); +#endif + json_base_class_t::operator=(std::move(other)); set_parents(); @@ -20765,13 +21504,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// get a pointer to the value (integer number) number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (integer number) constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept { - return is_number_integer() ? &m_data.m_value.number_integer : nullptr; + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; } /// get a pointer to the value (unsigned number) @@ -20906,7 +21645,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), std::declval()))) + JSONSerializer::from_json(std::declval(), std::declval()))) { auto ret = ValueType(); JSONSerializer::from_json(*this, ret); @@ -20948,7 +21687,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_non_default_from_json::value, int > = 0 > ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( - JSONSerializer::from_json(std::declval()))) + JSONSerializer::from_json(std::declval()))) { return JSONSerializer::from_json(*this); } @@ -21098,7 +21837,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::has_from_json::value, int > = 0 > ValueType & get_to(ValueType& v) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), v))) + JSONSerializer::from_json(std::declval(), v))) { JSONSerializer::from_json(*this, v); return v; @@ -21250,7 +21989,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21273,7 +22012,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { // create better exception explanation JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); - } + } // cppcheck-suppress[missingReturn] } else { @@ -21418,7 +22157,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief access specified object element /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ - reference operator[](typename object_t::key_type key) + reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -21728,7 +22467,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType pos) + IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) @@ -21798,7 +22537,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < class IteratorType, detail::enable_if_t < std::is_same::value || std::is_same::value, int > = 0 > - IteratorType erase(IteratorType first, IteratorType last) + IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param) { // make sure iterator fits the current value if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) @@ -22565,7 +23304,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @note: This uses std::distance to support GCC 4.8, /// see https://github.com/nlohmann/json/pull/1257 template - iterator insert_iterator(const_iterator pos, Args&& ... args) + iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param) { iterator result(this); JSON_ASSERT(m_data.m_value.array != nullptr); @@ -22584,7 +23323,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const basic_json& val) + iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22604,14 +23343,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, basic_json&& val) + iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) { return insert(pos, val); } /// @brief inserts copies of element into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_LIKELY(is_array())) @@ -22631,7 +23370,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) + iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22662,7 +23401,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts elements from initializer list into array /// @sa https://json.nlohmann.me/api/basic_json/insert/ - iterator insert(const_iterator pos, initializer_list_t ilist) + iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param) { // insert only works for arrays if (JSON_HEDLEY_UNLIKELY(!is_array())) @@ -22682,7 +23421,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief inserts range of elements into object /// @sa https://json.nlohmann.me/api/basic_json/insert/ - void insert(const_iterator first, const_iterator last) + void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) { // insert only works for objects if (JSON_HEDLEY_UNLIKELY(!is_object())) @@ -22703,6 +23442,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + set_parents(); } /// @brief updates a JSON object from another object, overwriting existing keys @@ -22714,7 +23454,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @brief updates a JSON object from another object, overwriting existing keys /// @sa https://json.nlohmann.me/api/basic_json/update/ - void update(const_iterator first, const_iterator last, bool merge_objects = false) + void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param) { // implicitly convert null value to an empty object if (is_null()) @@ -23315,12 +24055,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(InputType&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] return result; } @@ -23330,24 +24070,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) static basic_json parse(detail::span_input_adapter&& i, - const parser_callback_t cb = nullptr, + parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false) { basic_json result; - parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + parser(i.get(), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved] return result; } @@ -23526,6 +24266,23 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec basic_json* m_parent = nullptr; #endif +#if JSON_DIAGNOSTIC_POSITIONS + /// the start position of the value + std::size_t start_position = std::string::npos; + /// the end position of the value + std::size_t end_position = std::string::npos; + public: + constexpr std::size_t start_pos() const noexcept + { + return start_position; + } + + constexpr std::size_t end_pos() const noexcept + { + return end_position; + } +#endif + ////////////////////////////////////////// // binary serialization/deserialization // ////////////////////////////////////////// @@ -23611,27 +24368,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static std::vector to_bjdata(const basic_json& j, const bool use_size = false, - const bool use_type = false) + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { std::vector result; - to_bjdata(j, result, use_size, use_type); + to_bjdata(j, result, use_size, use_type, version); return result; } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BJData serialization of a given JSON value /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ static void to_bjdata(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) { - binary_writer(o).write_ubjson(j, use_size, use_type, true, true); + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); } /// @brief create a BSON serialization of a given JSON value @@ -23667,9 +24427,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23683,9 +24443,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23708,10 +24468,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23724,9 +24484,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23739,9 +24499,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23762,10 +24522,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23778,9 +24538,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23793,9 +24553,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23816,10 +24576,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23832,9 +24592,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23847,9 +24607,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23862,9 +24622,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23877,9 +24637,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } @@ -23900,10 +24660,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec const bool allow_exceptions = true) { basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) - const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] return res ? result : basic_json(value_t::discarded); } /// @} @@ -24004,7 +24764,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // the valid JSON Patch operations enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - const auto get_op = [](const std::string & op) + const auto get_op = [](const string_t& op) { if (op == "add") { @@ -24035,7 +24795,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec }; // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) + const auto operation_add = [&result](json_pointer & ptr, const basic_json & val) { // adding to the root of the target document means replacing it if (ptr.empty()) @@ -24141,15 +24901,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (const auto& val : json_patch) { // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, + const auto get_value = [&val](const string_t& op, + const string_t& member, bool string_type) -> basic_json & { // find value auto it = val.m_data.m_value.object->find(member); // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); + const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); // NOLINT(bugprone-unused-local-non-trivial-variable) // check if desired value is present if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) @@ -24176,8 +24936,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } // collect mandatory members - const auto op = get_value("op", "op", true).template get(); - const auto path = get_value(op, "path", true).template get(); + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); json_pointer ptr(path); switch (get_op(op)) @@ -24203,7 +24963,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::move: { - const auto from_path = get_value("move", "from", true).template get(); + const auto from_path = get_value("move", "from", true).template get(); json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24220,7 +24980,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec case patch_operations::copy: { - const auto from_path = get_value("copy", "from", true).template get(); + const auto from_path = get_value("copy", "from", true).template get(); const json_pointer from_ptr(from_path); // the "from" location must exist - use at() @@ -24280,7 +25040,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/diff/ JSON_HEDLEY_WARN_UNUSED_RESULT static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") + const string_t& path = "") { // the patch basic_json result(value_t::array); @@ -24310,7 +25070,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec while (i < source.size() && i < target.size()) { // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', std::to_string(i))); + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', detail::to_string(i))); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); ++i; } @@ -24327,7 +25087,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.insert(result.begin() + end_index, object( { {"op", "remove"}, - {"path", detail::concat(path, '/', std::to_string(i))} + {"path", detail::concat(path, '/', detail::to_string(i))} })); ++i; } @@ -24338,7 +25098,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec result.push_back( { {"op", "add"}, - {"path", detail::concat(path, "/-")}, + {"path", detail::concat(path, "/-")}, {"value", target[i]} }); ++i; @@ -24353,7 +25113,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec for (auto it = source.cbegin(); it != source.cend(); ++it) { // escape the key name to be used in a JSON patch - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); if (target.find(it.key()) != target.end()) { @@ -24377,7 +25137,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (source.find(it.key()) == source.end()) { // found a key that is not in this -> add it - const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); result.push_back( { {"op", "add"}, {"path", path_key}, @@ -24558,10 +25318,10 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT @@ -24592,6 +25352,7 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC #undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_20 + #undef JSON_HAS_CPP_23 #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON @@ -24603,10 +25364,10 @@ inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC // #include // __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.11.3 +// | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // -// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann // SPDX-License-Identifier: MIT diff --git a/fuzz/fuzz.cpp b/fuzz/fuzz.cpp new file mode 100644 index 0000000..909a95b --- /dev/null +++ b/fuzz/fuzz.cpp @@ -0,0 +1,6 @@ +#include "racfu.h" + +extern "C" int LLVMFuzzerTestOneInput(const char *request_json, int length) { + racfu(request_json, length, false); + return 0; +} diff --git a/racfu/python/_racfu.c b/racfu/python/_racfu.c index 1286f59..ed5078d 100644 --- a/racfu/python/_racfu.c +++ b/racfu/python/_racfu.c @@ -1,33 +1,46 @@ #define PY_SSIZE_T_CLEAN #include +#include #include #include "racfu.h" +pthread_mutex_t racfu_mutex = PTHREAD_MUTEX_INITIALIZER; + // Entry point to the call_racfu() function static PyObject* call_racfu(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject* result_dictionary; PyObject* debug_pyobj; const char* request_as_string; + Py_ssize_t request_length; bool debug = false; static char* kwlist[] = {"request", "debug", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, - &request_as_string, &debug_pyobj)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|O", kwlist, + &request_as_string, &request_length, + &debug_pyobj)) { return NULL; } - debug = PyObject_IsTrue(debug_pyobj); + debug = PyObject_IsTrue(debug_pyobj); + + // Since RACFu manages racfu_result_t as a static structure, + // we need to use a mutex to make this thread safe. + // Technically we shouldn't need this because the Python GIL, + // but we will set this up anyways to be safe. + pthread_mutex_lock(&racfu_mutex); - racfu_result_t* result = racfu(request_as_string, debug); + racfu_result_t* result = racfu(request_as_string, request_length, debug); result_dictionary = Py_BuildValue( "{s:y#,s:y#,s:s}", "raw_request", result->raw_request, result->raw_request_length, "raw_result", result->raw_result, result->raw_result_length, "result_json", result->result_json); + pthread_mutex_unlock(&racfu_mutex); + return result_dictionary; } diff --git a/racfu/racfu.cpp b/racfu/racfu.cpp index 2cc7192..ed352f9 100644 --- a/racfu/racfu.cpp +++ b/racfu/racfu.cpp @@ -2,10 +2,10 @@ #include "security_admin.hpp" -racfu_result_t *racfu(const char *request_json, bool debug) { +racfu_result_t *racfu(const char *request_json, int length, bool debug) { static racfu_result_t racfu_result = {nullptr, 0, nullptr, 0, nullptr}; RACFu::SecurityAdmin security_admin = RACFu::SecurityAdmin(&racfu_result, debug); - security_admin.makeRequest(request_json); + security_admin.makeRequest(request_json, length); return &racfu_result; } diff --git a/racfu/racfu.h b/racfu/racfu.h index 8180262..389306f 100644 --- a/racfu/racfu.h +++ b/racfu/racfu.h @@ -7,7 +7,7 @@ extern "C" { #endif -racfu_result_t *racfu(const char *request_json, bool debug); +racfu_result_t *racfu(const char *request_json, int length, bool debug); #ifdef __cplusplus } diff --git a/racfu/security_admin.cpp b/racfu/security_admin.cpp index 7854b29..9973514 100644 --- a/racfu/security_admin.cpp +++ b/racfu/security_admin.cpp @@ -2,6 +2,7 @@ #include +#include #include #include "irrsmo00.hpp" @@ -18,13 +19,17 @@ SecurityAdmin::SecurityAdmin(racfu_result_t *p_result, bool debug) { request_ = SecurityRequest(p_result); } -void SecurityAdmin::makeRequest(const char *p_request_json_string) { +void SecurityAdmin::makeRequest(const char *p_request_json_string, int length) { nlohmann::json request_json; try { + // Ensure Request JSON is a NULL terminated string. + auto request_json_unique_ptr = std::make_unique(length + 1); + std::memset(request_json_unique_ptr.get(), 0, length + 1); + std::strncpy(request_json_unique_ptr.get(), p_request_json_string, length); // Parse Request JSON try { - request_json = nlohmann::json::parse(p_request_json_string); + request_json = nlohmann::json::parse(request_json_unique_ptr.get()); } catch (const nlohmann::json::parse_error &ex) { request_.setRACFuReturnCode(8); throw RACFuError(std::string("Syntax error in request JSON at byte ") + diff --git a/racfu/security_admin.hpp b/racfu/security_admin.hpp index d1c4232..b5b511a 100644 --- a/racfu/security_admin.hpp +++ b/racfu/security_admin.hpp @@ -23,7 +23,7 @@ class SecurityAdmin { public: SecurityAdmin(racfu_result_t *p_result, bool debug); - void makeRequest(const char *p_request_json_string); + void makeRequest(const char *p_request_json_string, int length); }; } // namespace RACFu diff --git a/tests/irrsmo00/test_irrsmo00.cpp b/tests/irrsmo00/test_irrsmo00.cpp index 4ebcd06..c8ea846 100644 --- a/tests/irrsmo00/test_irrsmo00.cpp +++ b/tests/irrsmo00/test_irrsmo00.cpp @@ -75,7 +75,8 @@ void test_parse_add_user_no_xml_data_error() { irrsmo64_racf_rc_mock = 200; irrsmo64_racf_reason_mock = 16; - racfu_result_t *result = racfu(request_json.c_str(), false); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); @@ -94,7 +95,8 @@ void test_parse_alter_user_no_xml_data_error() { irrsmo64_racf_rc_mock = 4; irrsmo64_racf_reason_mock = 0; - racfu_result_t *result = racfu(request_json.c_str(), false); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); @@ -121,7 +123,8 @@ void test_parse_irrsmo00_errors_result() { irrsmo64_racf_rc_mock = 2000; irrsmo64_racf_reason_mock = 68; - racfu_result_t *result = racfu(request_json.c_str(), false); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), diff --git a/tests/unit_test_utilities.cpp b/tests/unit_test_utilities.cpp index 46e0cf1..8e1a395 100644 --- a/tests/unit_test_utilities.cpp +++ b/tests/unit_test_utilities.cpp @@ -99,7 +99,8 @@ void test_validation_errors(const char *test_request_json, std::string result_json_expected = get_json_sample(test_validation_errors_result_json); - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); } @@ -121,10 +122,11 @@ void test_extract_request_generation(const char *test_extract_request_json, r_admin_racf_rc_mock = 0; r_admin_racf_reason_mock = 0; - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); - int request_buffer_size = TEST_IRRSEQ00_GENERIC_REQUEST_BUFFER_SIZE; - int arg_area_size = TEST_IRRSEQ00_GENERIC_ARG_AREA_SIZE; + int request_buffer_size = TEST_IRRSEQ00_GENERIC_REQUEST_BUFFER_SIZE; + int arg_area_size = TEST_IRRSEQ00_GENERIC_ARG_AREA_SIZE; if (racf_options == true) { request_buffer_size = TEST_IRRSEQ00_RACF_OPTIONS_REQUEST_BUFFER_SIZE; @@ -160,7 +162,8 @@ void test_parse_extract_result(const char *test_extract_request_json, r_admin_racf_rc_mock = 0; r_admin_racf_reason_mock = 0; - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), @@ -189,7 +192,8 @@ void test_parse_extract_result_profile_not_found( r_admin_racf_rc_mock = 4; r_admin_racf_reason_mock = 4; - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), @@ -313,7 +317,8 @@ void test_generate_add_alter_delete_request_generation( irrsmo64_racf_rc_mock = 0; irrsmo64_racf_reason_mock = 0; - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); TEST_ASSERT_EQUAL_INT32(raw_request_length_expected, result->raw_request_length); @@ -344,7 +349,8 @@ void test_parse_add_alter_delete_result( irrsmo64_racf_rc_mock = 0; irrsmo64_racf_reason_mock = 0; - racfu_result_t *result = racfu(request_json.c_str(), debug); + racfu_result_t *result = + racfu(request_json.c_str(), request_json.length(), debug); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), diff --git a/tests/unity/LICENSE.txt b/tests/unity/LICENSE.txt index b9a329d..3e3aad5 100644 --- a/tests/unity/LICENSE.txt +++ b/tests/unity/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams +Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tests/unity/unity.c b/tests/unity/unity.c index be3528f..7fa2dc6 100644 --- a/tests/unity/unity.c +++ b/tests/unity/unity.c @@ -1,16 +1,14 @@ /* ========================================================================= - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -============================================================================ */ + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ #include "unity.h" -#include -#ifdef AVR -#include -#else -#define PROGMEM +#ifndef UNITY_PROGMEM +#define UNITY_PROGMEM #endif /* If omitted from header, declare overrideable prototypes here so they're ready for use */ @@ -19,57 +17,57 @@ void UNITY_OUTPUT_CHAR(int); #endif /* Helpful macros for us to use here in Assert functions */ -#define UNITY_FAIL_AND_BAIL { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } -#define UNITY_IGNORE_AND_BAIL { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } -#define RETURN_IF_FAIL_OR_IGNORE if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) TEST_ABORT() +#define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) +#define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0) struct UNITY_STORAGE_T Unity; #ifdef UNITY_OUTPUT_COLOR -const char PROGMEM UnityStrOk[] = "\033[42mOK\033[00m"; -const char PROGMEM UnityStrPass[] = "\033[42mPASS\033[00m"; -const char PROGMEM UnityStrFail[] = "\033[41mFAIL\033[00m"; -const char PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[00m"; +const char UNITY_PROGMEM UnityStrOk[] = "\033[42mOK\033[0m"; +const char UNITY_PROGMEM UnityStrPass[] = "\033[42mPASS\033[0m"; +const char UNITY_PROGMEM UnityStrFail[] = "\033[41mFAIL\033[0m"; +const char UNITY_PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[0m"; #else -const char PROGMEM UnityStrOk[] = "OK"; -const char PROGMEM UnityStrPass[] = "PASS"; -const char PROGMEM UnityStrFail[] = "FAIL"; -const char PROGMEM UnityStrIgnore[] = "IGNORE"; +const char UNITY_PROGMEM UnityStrOk[] = "OK"; +const char UNITY_PROGMEM UnityStrPass[] = "PASS"; +const char UNITY_PROGMEM UnityStrFail[] = "FAIL"; +const char UNITY_PROGMEM UnityStrIgnore[] = "IGNORE"; #endif -static const char PROGMEM UnityStrNull[] = "NULL"; -static const char PROGMEM UnityStrSpacer[] = ". "; -static const char PROGMEM UnityStrExpected[] = " Expected "; -static const char PROGMEM UnityStrWas[] = " Was "; -static const char PROGMEM UnityStrGt[] = " to be greater than "; -static const char PROGMEM UnityStrLt[] = " to be less than "; -static const char PROGMEM UnityStrOrEqual[] = "or equal to "; -static const char PROGMEM UnityStrNotEqual[] = " to be not equal to "; -static const char PROGMEM UnityStrElement[] = " Element "; -static const char PROGMEM UnityStrByte[] = " Byte "; -static const char PROGMEM UnityStrMemory[] = " Memory Mismatch."; -static const char PROGMEM UnityStrDelta[] = " Values Not Within Delta "; -static const char PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; -static const char PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; -static const char PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; +static const char UNITY_PROGMEM UnityStrNull[] = "NULL"; +static const char UNITY_PROGMEM UnityStrSpacer[] = ". "; +static const char UNITY_PROGMEM UnityStrExpected[] = " Expected "; +static const char UNITY_PROGMEM UnityStrWas[] = " Was "; +static const char UNITY_PROGMEM UnityStrGt[] = " to be greater than "; +static const char UNITY_PROGMEM UnityStrLt[] = " to be less than "; +static const char UNITY_PROGMEM UnityStrOrEqual[] = "or equal to "; +static const char UNITY_PROGMEM UnityStrNotEqual[] = " to be not equal to "; +static const char UNITY_PROGMEM UnityStrElement[] = " Element "; +static const char UNITY_PROGMEM UnityStrByte[] = " Byte "; +static const char UNITY_PROGMEM UnityStrMemory[] = " Memory Mismatch."; +static const char UNITY_PROGMEM UnityStrDelta[] = " Values Not Within Delta "; +static const char UNITY_PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; +static const char UNITY_PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; +static const char UNITY_PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; #ifndef UNITY_EXCLUDE_FLOAT -static const char PROGMEM UnityStrNot[] = "Not "; -static const char PROGMEM UnityStrInf[] = "Infinity"; -static const char PROGMEM UnityStrNegInf[] = "Negative Infinity"; -static const char PROGMEM UnityStrNaN[] = "NaN"; -static const char PROGMEM UnityStrDet[] = "Determinate"; -static const char PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; +static const char UNITY_PROGMEM UnityStrNot[] = "Not "; +static const char UNITY_PROGMEM UnityStrInf[] = "Infinity"; +static const char UNITY_PROGMEM UnityStrNegInf[] = "Negative Infinity"; +static const char UNITY_PROGMEM UnityStrNaN[] = "NaN"; +static const char UNITY_PROGMEM UnityStrDet[] = "Determinate"; +static const char UNITY_PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; #endif -const char PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; -const char PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; -const char PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; -const char PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; -static const char PROGMEM UnityStrBreaker[] = "-----------------------"; -static const char PROGMEM UnityStrResultsTests[] = " Tests "; -static const char PROGMEM UnityStrResultsFailures[] = " Failures "; -static const char PROGMEM UnityStrResultsIgnored[] = " Ignored "; +const char UNITY_PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; +const char UNITY_PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; +const char UNITY_PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; +const char UNITY_PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; +static const char UNITY_PROGMEM UnityStrBreaker[] = "-----------------------"; +static const char UNITY_PROGMEM UnityStrResultsTests[] = " Tests "; +static const char UNITY_PROGMEM UnityStrResultsFailures[] = " Failures "; +static const char UNITY_PROGMEM UnityStrResultsIgnored[] = " Ignored "; #ifndef UNITY_EXCLUDE_DETAILS -static const char PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; -static const char PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; +static const char UNITY_PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; +static const char UNITY_PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; #endif /*----------------------------------------------- * Pretty Printers & Test Result Output Handlers @@ -359,20 +357,22 @@ void UnityPrintFloat(const UNITY_DOUBLE input_number) { UnityPrint("0"); } - else if (isnan(number)) + else if (UNITY_IS_NAN(number)) { UnityPrint("nan"); } - else if (isinf(number)) + else if (UNITY_IS_INF(number)) { UnityPrint("inf"); } else { - UNITY_INT32 n_int = 0, n; - int exponent = 0; - int decimals, digits; - char buf[16] = {0}; + UNITY_INT32 n_int = 0; + UNITY_INT32 n; + int exponent = 0; + int decimals; + int digits; + char buf[16] = {0}; /* * Scale up or down by powers of 10. To minimize rounding error, @@ -445,14 +445,19 @@ void UnityPrintFloat(const UNITY_DOUBLE input_number) /* build up buffer in reverse order */ digits = 0; - while ((n != 0) || (digits < (decimals + 1))) + while ((n != 0) || (digits <= decimals)) { buf[digits++] = (char)('0' + n % 10); n /= 10; } + + /* print out buffer (backwards) */ while (digits > 0) { - if (digits == decimals) { UNITY_OUTPUT_CHAR('.'); } + if (digits == decimals) + { + UNITY_OUTPUT_CHAR('.'); + } UNITY_OUTPUT_CHAR(buf[--digits]); } @@ -564,26 +569,26 @@ void UnityConcludeTest(void) /*-----------------------------------------------*/ static void UnityAddMsgIfSpecified(const char* msg) { - if (msg) - { - UnityPrint(UnityStrSpacer); - #ifdef UNITY_PRINT_TEST_CONTEXT - UNITY_PRINT_TEST_CONTEXT(); + UnityPrint(UnityStrSpacer); + UNITY_PRINT_TEST_CONTEXT(); #endif #ifndef UNITY_EXCLUDE_DETAILS - if (Unity.CurrentDetail1) + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrSpacer); + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) { - UnityPrint(UnityStrDetail1Name); - UnityPrint(Unity.CurrentDetail1); - if (Unity.CurrentDetail2) - { - UnityPrint(UnityStrDetail2Name); - UnityPrint(Unity.CurrentDetail2); - } - UnityPrint(UnityStrSpacer); + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); } + } #endif + if (msg) + { + UnityPrint(UnityStrSpacer); UnityPrint(msg); } } @@ -765,11 +770,12 @@ void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, } #define UnityPrintPointlessAndBail() \ -{ \ +do { \ UnityTestResultsFailBegin(lineNumber); \ UnityPrint(UnityStrPointless); \ UnityAddMsgIfSpecified(msg); \ - UNITY_FAIL_AND_BAIL; } + UNITY_FAIL_AND_BAIL; \ +} while (0) /*-----------------------------------------------*/ void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, @@ -788,7 +794,11 @@ void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, if (num_elements == 0) { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else UnityPrintPointlessAndBail(); +#endif } if (expected == actual) @@ -811,12 +821,22 @@ void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, case 1: expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x000000FF; + actual_val &= 0x000000FF; + } increment = sizeof(UNITY_INT8); break; case 2: expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x0000FFFF; + actual_val &= 0x0000FFFF; + } increment = sizeof(UNITY_INT16); break; @@ -832,6 +852,13 @@ void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, case 4: expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; +#ifdef UNITY_SUPPORT_64 + if (style & (UNITY_DISPLAY_RANGE_UINT | UNITY_DISPLAY_RANGE_HEX)) + { + expect_val &= 0x00000000FFFFFFFF; + actual_val &= 0x00000000FFFFFFFF; + } +#endif increment = sizeof(UNITY_INT32); length = 4; break; @@ -869,26 +896,27 @@ void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, #ifndef UNITY_EXCLUDE_FLOAT /* Wrap this define in a function with variable types as float or double */ #define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ - if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ + if (UNITY_IS_INF(expected) && UNITY_IS_INF(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ if (UNITY_NAN_CHECK) return 1; \ (diff) = (actual) - (expected); \ if ((diff) < 0) (diff) = -(diff); \ if ((delta) < 0) (delta) = -(delta); \ - return !(isnan(diff) || isinf(diff) || ((diff) > (delta))) + return !(UNITY_IS_NAN(diff) || UNITY_IS_INF(diff) || ((diff) > (delta))) /* This first part of this condition will catch any NaN or Infinite values */ #ifndef UNITY_NAN_NOT_EQUAL_NAN - #define UNITY_NAN_CHECK isnan(expected) && isnan(actual) + #define UNITY_NAN_CHECK UNITY_IS_NAN(expected) && UNITY_IS_NAN(actual) #else #define UNITY_NAN_CHECK 0 #endif #ifndef UNITY_EXCLUDE_FLOAT_PRINT #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ - { \ + do { \ UnityPrint(UnityStrExpected); \ UnityPrintFloat(expected); \ UnityPrint(UnityStrWas); \ - UnityPrintFloat(actual); } + UnityPrintFloat(actual); \ + } while (0) #else #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ UnityPrint(UnityStrDelta) @@ -902,21 +930,39 @@ static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOA } /*-----------------------------------------------*/ -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) +void UnityAssertWithinFloatArray(const UNITY_FLOAT delta, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) { UNITY_UINT32 elements = num_elements; UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; + UNITY_FLOAT in_delta = delta; + UNITY_FLOAT current_element_delta = delta; RETURN_IF_FAIL_OR_IGNORE; if (elements == 0) { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (UNITY_IS_INF(in_delta)) + { + return; /* Arrays will be force equal with infinite delta */ + } + + if (UNITY_IS_NAN(in_delta)) + { + /* Delta must be correct number */ UnityPrintPointlessAndBail(); } @@ -930,9 +976,23 @@ void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, UNITY_FAIL_AND_BAIL; } + /* fix delta sign if need */ + if (in_delta < 0) + { + in_delta = -in_delta; + } + while (elements--) { - if (!UnityFloatsWithin(*ptr_expected * UNITY_FLOAT_PRECISION, *ptr_expected, *ptr_actual)) + current_element_delta = *ptr_expected * UNITY_FLOAT_PRECISION; + + if (current_element_delta < 0) + { + /* fix delta sign for correct calculations */ + current_element_delta = -current_element_delta; + } + + if (!UnityFloatsWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) { UnityTestResultsFailBegin(lineNumber); UnityPrint(UnityStrElement); @@ -968,6 +1028,60 @@ void UnityAssertFloatsWithin(const UNITY_FLOAT delta, } } +/*-----------------------------------------------*/ +void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (UnityFloatsWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat((UNITY_DOUBLE)expected); + UnityPrint(UnityStrNotEqual); + UnityPrintFloat((UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold, + const UNITY_FLOAT actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + int failed; + + RETURN_IF_FAIL_OR_IGNORE; + + failed = 0; + + /* Checking for "not success" rather than failure to get the right result for NaN */ + if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + + if ((compare & UNITY_EQUAL_TO) && UnityFloatsWithin(threshold * UNITY_FLOAT_PRECISION, threshold, actual)) { failed = 0; } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat(actual); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + UnityPrintFloat(threshold); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + /*-----------------------------------------------*/ void UnityAssertFloatSpecial(const UNITY_FLOAT actual, const char* msg, @@ -985,23 +1099,24 @@ void UnityAssertFloatSpecial(const UNITY_FLOAT actual, { case UNITY_FLOAT_IS_INF: case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); + is_trait = UNITY_IS_INF(actual) && (actual > 0); break; case UNITY_FLOAT_IS_NEG_INF: case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); + is_trait = UNITY_IS_INF(actual) && (actual < 0); break; case UNITY_FLOAT_IS_NAN: case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; + is_trait = UNITY_IS_NAN(actual) ? 1 : 0; break; case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); + is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); break; + case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ default: /* including UNITY_FLOAT_INVALID_TRAIT */ trait_index = 0; trait_names[0] = UnityStrInvalidFloatTrait; @@ -1043,21 +1158,39 @@ static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_D } /*-----------------------------------------------*/ -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) +void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) { UNITY_UINT32 elements = num_elements; UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; + UNITY_DOUBLE in_delta = delta; + UNITY_DOUBLE current_element_delta = delta; RETURN_IF_FAIL_OR_IGNORE; if (elements == 0) { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + + if (UNITY_IS_INF(in_delta)) + { + return; /* Arrays will be force equal with infinite delta */ + } + + if (UNITY_IS_NAN(in_delta)) + { + /* Delta must be correct number */ UnityPrintPointlessAndBail(); } @@ -1071,9 +1204,23 @@ void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expecte UNITY_FAIL_AND_BAIL; } + /* fix delta sign if need */ + if (in_delta < 0) + { + in_delta = -in_delta; + } + while (elements--) { - if (!UnityDoublesWithin(*ptr_expected * UNITY_DOUBLE_PRECISION, *ptr_expected, *ptr_actual)) + current_element_delta = *ptr_expected * UNITY_DOUBLE_PRECISION; + + if (current_element_delta < 0) + { + /* fix delta sign for correct calculations */ + current_element_delta = -current_element_delta; + } + + if (!UnityDoublesWithin(in_delta + current_element_delta, *ptr_expected, *ptr_actual)) { UnityTestResultsFailBegin(lineNumber); UnityPrint(UnityStrElement); @@ -1108,6 +1255,60 @@ void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, } } +/*-----------------------------------------------*/ +void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (UnityDoublesWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat((UNITY_DOUBLE)expected); + UnityPrint(UnityStrNotEqual); + UnityPrintFloat((UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold, + const UNITY_DOUBLE actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + int failed; + + RETURN_IF_FAIL_OR_IGNORE; + + failed = 0; + + /* Checking for "not success" rather than failure to get the right result for NaN */ + if (!(actual < threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } + if (!(actual > threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } + + if ((compare & UNITY_EQUAL_TO) && UnityDoublesWithin(threshold * UNITY_DOUBLE_PRECISION, threshold, actual)) { failed = 0; } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintFloat(actual); + if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } + if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } + if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } + UnityPrintFloat(threshold); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + /*-----------------------------------------------*/ void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, const char* msg, @@ -1125,23 +1326,24 @@ void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, { case UNITY_FLOAT_IS_INF: case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); + is_trait = UNITY_IS_INF(actual) && (actual > 0); break; case UNITY_FLOAT_IS_NEG_INF: case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); + is_trait = UNITY_IS_INF(actual) && (actual < 0); break; case UNITY_FLOAT_IS_NAN: case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; + is_trait = UNITY_IS_NAN(actual) ? 1 : 0; break; case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); + is_trait = !UNITY_IS_INF(actual) && !UNITY_IS_NAN(actual); break; + case UNITY_FLOAT_INVALID_TRAIT: /* Supress warning */ default: /* including UNITY_FLOAT_INVALID_TRAIT */ trait_index = 0; trait_names[0] = UnityStrInvalidFloatTrait; @@ -1239,7 +1441,11 @@ void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, if (num_elements == 0) { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else UnityPrintPointlessAndBail(); +#endif } if (expected == actual) @@ -1260,30 +1466,70 @@ void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, switch (length) { case 1: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; - increment = sizeof(UNITY_INT8); + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + increment = sizeof(UNITY_INT8); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT8*)actual; + increment = sizeof(UNITY_UINT8); + } break; case 2: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; - increment = sizeof(UNITY_INT16); + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + increment = sizeof(UNITY_INT16); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT16*)actual; + increment = sizeof(UNITY_UINT16); + } break; #ifdef UNITY_SUPPORT_64 case 8: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; - increment = sizeof(UNITY_INT64); + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + increment = sizeof(UNITY_INT64); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT64*)actual; + increment = sizeof(UNITY_UINT64); + } break; #endif default: /* default is length 4 bytes */ case 4: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; - increment = sizeof(UNITY_INT32); + /* fixing problems with signed overflow on unsigned numbers */ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; + increment = sizeof(UNITY_INT32); + } + else + { + expect_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)expected; + actual_val = (UNITY_INT)*(UNITY_PTR_ATTRIBUTE const UNITY_UINT32*)actual; + increment = sizeof(UNITY_UINT32); + } length = 4; break; } @@ -1364,8 +1610,8 @@ void UnityAssertEqualString(const char* expected, } } else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) + { /* fail if either null but not if both */ + if (expected || actual) { Unity.CurrentTestFailed = 1; } @@ -1404,8 +1650,8 @@ void UnityAssertEqualStringLen(const char* expected, } } else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) + { /* fail if either null but not if both */ + if (expected || actual) { Unity.CurrentTestFailed = 1; } @@ -1438,7 +1684,11 @@ void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, /* if no elements, it's an error */ if (num_elements == 0) { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else UnityPrintPointlessAndBail(); +#endif } if ((const void*)expected == (const void*)actual) @@ -1515,7 +1765,15 @@ void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, RETURN_IF_FAIL_OR_IGNORE; - if ((elements == 0) || (length == 0)) + if (elements == 0) + { +#ifdef UNITY_COMPARE_PTRS_ON_ZERO_ARRAY + UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, lineNumber, msg); +#else + UnityPrintPointlessAndBail(); +#endif + } + if (length == 0) { UnityPrintPointlessAndBail(); } @@ -1623,10 +1881,96 @@ UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) } #endif +#ifdef UNITY_INCLUDE_PRINT_FORMATTED + +/*----------------------------------------------- + * printf length modifier helpers + *-----------------------------------------------*/ + +enum UnityLengthModifier { + UNITY_LENGTH_MODIFIER_NONE, + UNITY_LENGTH_MODIFIER_LONG_LONG, + UNITY_LENGTH_MODIFIER_LONG, +}; + +#define UNITY_EXTRACT_ARG(NUMBER_T, NUMBER, LENGTH_MOD, VA, ARG_T) \ +do { \ + switch (LENGTH_MOD) \ + { \ + case UNITY_LENGTH_MODIFIER_LONG_LONG: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, long long ARG_T); \ + break; \ + } \ + case UNITY_LENGTH_MODIFIER_LONG: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, long ARG_T); \ + break; \ + } \ + case UNITY_LENGTH_MODIFIER_NONE: \ + default: \ + { \ + NUMBER = (NUMBER_T)va_arg(VA, ARG_T); \ + break; \ + } \ + } \ +} while (0) + +static enum UnityLengthModifier UnityLengthModifierGet(const char *pch, int *length) +{ + enum UnityLengthModifier length_mod; + switch (pch[0]) + { + case 'l': + { + if (pch[1] == 'l') + { + *length = 2; + length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; + } + else + { + *length = 1; + length_mod = UNITY_LENGTH_MODIFIER_LONG; + } + break; + } + case 'h': + { + // short and char are converted to int + length_mod = UNITY_LENGTH_MODIFIER_NONE; + if (pch[1] == 'h') + { + *length = 2; + } + else + { + *length = 1; + } + break; + } + case 'j': + case 'z': + case 't': + case 'L': + { + // Not supported, but should gobble up the length specifier anyway + length_mod = UNITY_LENGTH_MODIFIER_NONE; + *length = 1; + break; + } + default: + { + length_mod = UNITY_LENGTH_MODIFIER_NONE; + *length = 0; + } + } + return length_mod; +} + /*----------------------------------------------- * printf helper function *-----------------------------------------------*/ -#ifdef UNITY_INCLUDE_PRINT_FORMATTED static void UnityPrintFVA(const char* format, va_list va) { const char* pch = format; @@ -1641,12 +1985,17 @@ static void UnityPrintFVA(const char* format, va_list va) if (pch != NULL) { + int length_mod_size; + enum UnityLengthModifier length_mod = UnityLengthModifierGet(pch, &length_mod_size); + pch += length_mod_size; + switch (*pch) { case 'd': case 'i': { - const int number = va_arg(va, int); + UNITY_INT number; + UNITY_EXTRACT_ARG(UNITY_INT, number, length_mod, va, int); UnityPrintNumber((UNITY_INT)number); break; } @@ -1661,27 +2010,44 @@ static void UnityPrintFVA(const char* format, va_list va) #endif case 'u': { - const unsigned int number = va_arg(va, unsigned int); - UnityPrintNumberUnsigned((UNITY_UINT)number); + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + UnityPrintNumberUnsigned(number); break; } case 'b': { - const unsigned int number = va_arg(va, unsigned int); + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1; UNITY_OUTPUT_CHAR('0'); UNITY_OUTPUT_CHAR('b'); - UnityPrintMask(mask, (UNITY_UINT)number); + UnityPrintMask(mask, number); break; } case 'x': case 'X': + { + UNITY_UINT number; + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex(number, UNITY_MAX_NIBBLES); + break; + } case 'p': { - const unsigned int number = va_arg(va, unsigned int); + UNITY_UINT number; + char nibbles_to_print = 8; + if (UNITY_POINTER_WIDTH == 64) + { + length_mod = UNITY_LENGTH_MODIFIER_LONG_LONG; + nibbles_to_print = 16; + } + UNITY_EXTRACT_ARG(UNITY_UINT, number, length_mod, va, unsigned int); UNITY_OUTPUT_CHAR('0'); UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)number, 8); + UnityPrintNumberHex((UNITY_UINT)number, nibbles_to_print); break; } case 'c': @@ -1848,7 +2214,7 @@ void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int /*-----------------------------------------------*/ void UnitySetTestFile(const char* filename) { - Unity.TestFile = filename; + Unity.TestFile = filename; } /*-----------------------------------------------*/ @@ -1905,6 +2271,7 @@ int UnityEnd(void) char* UnityOptionIncludeNamed = NULL; char* UnityOptionExcludeNamed = NULL; int UnityVerbosity = 1; +int UnityStrictMatch = 0; /*-----------------------------------------------*/ int UnityParseOptions(int argc, char** argv) @@ -1912,6 +2279,7 @@ int UnityParseOptions(int argc, char** argv) int i; UnityOptionIncludeNamed = NULL; UnityOptionExcludeNamed = NULL; + UnityStrictMatch = 0; for (i = 1; i < argc; i++) { @@ -1923,6 +2291,7 @@ int UnityParseOptions(int argc, char** argv) return -1; case 'n': /* include tests with name including this string */ case 'f': /* an alias for -n */ + UnityStrictMatch = (argv[i][1] == 'n'); /* strictly match this string if -n */ if (argv[i][2] == '=') { UnityOptionIncludeNamed = &argv[i][3]; @@ -1964,6 +2333,18 @@ int UnityParseOptions(int argc, char** argv) UnityPrint("ERROR: Unknown Option "); UNITY_OUTPUT_CHAR(argv[i][1]); UNITY_PRINT_EOL(); + /* Now display help */ + /* FALLTHRU */ + case 'h': + UnityPrint("Options: "); UNITY_PRINT_EOL(); + UnityPrint("-l List all tests and exit"); UNITY_PRINT_EOL(); + UnityPrint("-f NAME Filter to run only tests whose name includes NAME"); UNITY_PRINT_EOL(); + UnityPrint("-n NAME Run only the test named NAME"); UNITY_PRINT_EOL(); + UnityPrint("-h show this Help menu"); UNITY_PRINT_EOL(); + UnityPrint("-q Quiet/decrease verbosity"); UNITY_PRINT_EOL(); + UnityPrint("-v increase Verbosity"); UNITY_PRINT_EOL(); + UnityPrint("-x NAME eXclude tests whose name includes NAME"); UNITY_PRINT_EOL(); + UNITY_OUTPUT_FLUSH(); return 1; } } @@ -1973,7 +2354,7 @@ int UnityParseOptions(int argc, char** argv) } /*-----------------------------------------------*/ -int IsStringInBiggerString(const char* longstring, const char* shortstring) +static int IsStringInBiggerString(const char* longstring, const char* shortstring) { const char* lptr = longstring; const char* sptr = shortstring; @@ -1981,7 +2362,7 @@ int IsStringInBiggerString(const char* longstring, const char* shortstring) if (*sptr == '*') { - return 1; + return UnityStrictMatch ? 0 : 1; } while (*lptr) @@ -1994,19 +2375,29 @@ int IsStringInBiggerString(const char* longstring, const char* shortstring) lptr++; sptr++; - /* We're done if we match the entire string or up to a wildcard */ - if (*sptr == '*') - return 1; - if (*sptr == ',') - return 1; - if (*sptr == '"') - return 1; - if (*sptr == '\'') - return 1; - if (*sptr == ':') - return 2; - if (*sptr == 0) - return 1; + switch (*sptr) + { + case '*': /* we encountered a wild-card */ + return UnityStrictMatch ? 0 : 1; + + case ',': /* we encountered the end of match string */ + case '"': + case '\'': + case 0: + return (!UnityStrictMatch || (*lptr == 0)) ? 1 : 0; + + case ':': /* we encountered the end of a partial match */ + return 2; + + default: + break; + } + } + + // If we didn't match and we're on strict matching, we already know we failed + if (UnityStrictMatch) + { + return 0; } /* Otherwise we start in the long pointer 1 character further and try again */ @@ -2018,7 +2409,7 @@ int IsStringInBiggerString(const char* longstring, const char* shortstring) } /*-----------------------------------------------*/ -int UnityStringArgumentMatches(const char* str) +static int UnityStringArgumentMatches(const char* str) { int retval; const char* ptr1; diff --git a/tests/unity/unity.h b/tests/unity/unity.h index ab986c4..9e2a97b 100644 --- a/tests/unity/unity.h +++ b/tests/unity/unity.h @@ -1,16 +1,17 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ #ifndef UNITY_FRAMEWORK_H #define UNITY_FRAMEWORK_H #define UNITY #define UNITY_VERSION_MAJOR 2 -#define UNITY_VERSION_MINOR 5 -#define UNITY_VERSION_BUILD 2 +#define UNITY_VERSION_MINOR 6 +#define UNITY_VERSION_BUILD 1 #define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD) #ifdef __cplusplus @@ -89,7 +90,7 @@ void verifyTest(void); * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script * Parameterized Tests - * - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing + * - you'll want to create a define of TEST_CASE(...), TEST_RANGE(...) and/or TEST_MATRIX(...) which basically evaluates to nothing * Tests with Arguments * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity @@ -105,17 +106,27 @@ void verifyTest(void); #define TEST_MESSAGE(message) UnityMessage((message), __LINE__) #define TEST_ONLY() #ifdef UNITY_INCLUDE_PRINT_FORMATTED -#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), __VA_ARGS__) +#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), ##__VA_ARGS__) #endif /* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ #define TEST_PASS() TEST_ABORT() -#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while(0) +#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0) -/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out - * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */ -#define TEST_FILE(a) +/*------------------------------------------------------- + * Build Directives + *------------------------------------------------------- + + * These macros do nothing, but they are useful for additional build context. + * Tools (like Ceedling) can scan for these directives and make use of them for + * per-test-executable #include search paths and linking. */ + +/* Add source files to a test executable's compilation and linking. Ex: TEST_SOURCE_FILE("sandwiches.c") */ +#define TEST_SOURCE_FILE(a) + +/* Customize #include search paths for a test executable's compilation. Ex: TEST_INCLUDE_PATH("src/module_a/inc") */ +#define TEST_INCLUDE_PATH(a) /*------------------------------------------------------- * Test Asserts (simple) @@ -337,9 +348,16 @@ void verifyTest(void); /* Floating Point (If Enabled) */ #define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL) #define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL) #define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) #define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, NULL) #define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) #define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) #define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) @@ -351,9 +369,16 @@ void verifyTest(void); /* Double (If Enabled) */ #define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((delta), (expected), (actual), __LINE__, NULL) #define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, NULL) #define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) #define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, NULL) #define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) #define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) #define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) @@ -607,8 +632,14 @@ void verifyTest(void); /* Floating Point (If Enabled) */ #define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) #define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message)) #define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) #define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_FLOAT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_FLOAT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT((threshold), (actual), __LINE__, (message)) #define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) #define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) #define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) @@ -621,8 +652,14 @@ void verifyTest(void); /* Double (If Enabled) */ #define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) #define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN((delta), (expected), (actual), (num_elements), __LINE__, (message)) #define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) #define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_THAN_DOUBLE((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_DOUBLE_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE((threshold), (actual), __LINE__, (message)) #define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) #define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) #define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) diff --git a/tests/unity/unity_internals.h b/tests/unity/unity_internals.h index 79c305e..562f5b6 100644 --- a/tests/unity/unity_internals.h +++ b/tests/unity/unity_internals.h @@ -1,8 +1,9 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ +/* ========================================================================= + Unity - A Test Framework for C + ThrowTheSwitch.org + Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ #ifndef UNITY_INTERNALS_H #define UNITY_INTERNALS_H @@ -40,10 +41,57 @@ #include #endif -#if defined __GNUC__ -# define UNITY_FUNCTION_ATTR(a) __attribute__((a)) +#if defined(__GNUC__) || defined(__clang__) + #define UNITY_FUNCTION_ATTR(a) __attribute__((a)) #else -# define UNITY_FUNCTION_ATTR(a) /* ignore */ + #define UNITY_FUNCTION_ATTR(a) /* ignore */ +#endif + +/* UNITY_NORETURN is only required if we have setjmp.h. */ +#ifndef UNITY_EXCLUDE_SETJMP_H + #ifndef UNITY_NORETURN + #if defined(__cplusplus) + #if __cplusplus >= 201103L + #define UNITY_NORETURN [[ noreturn ]] + #endif + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && __STDC_VERSION__ < 202311L + /* _Noreturn keyword is used from C11 but deprecated in C23. */ + #if defined(_WIN32) && defined(_MSC_VER) + /* We are using MSVC compiler on Windows platform. */ + /* Not all Windows SDKs supports , but compiler can support C11: */ + /* https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-support-arriving-in-msvc/ */ + /* Not sure, that Mingw compilers has Windows SDK headers at all. */ + #include + #endif + + /* Using Windows SDK predefined macro for detecting supported SDK with MSVC compiler. */ + /* Mingw GCC should work without that fixes. */ + /* Based on: */ + /* https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 */ + /* NTDDI_WIN10_FE is equal to Windows 10 SDK 2104 */ + #if defined(_MSC_VER) && ((!defined(NTDDI_WIN10_FE)) || WDK_NTDDI_VERSION < NTDDI_WIN10_FE) + /* Based on tests and: */ + /* https://docs.microsoft.com/en-us/cpp/c-language/noreturn?view=msvc-170 */ + /* https://en.cppreference.com/w/c/language/_Noreturn */ + #define UNITY_NORETURN _Noreturn + #else /* Using newer Windows SDK or not MSVC compiler */ + #if defined(__GNUC__) + /* The header collides with __attribute(noreturn)__ from GCC. */ + #define UNITY_NORETURN _Noreturn + #else + #include + #define UNITY_NORETURN noreturn + #endif + #endif + #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L + /* Since C23, the keyword _Noreturn has been replaced by the attribute noreturn, based on: */ + /* https://en.cppreference.com/w/c/language/attributes/noreturn */ + #define UNITY_NORETURN [[ noreturn ]] + #endif + #endif + #ifndef UNITY_NORETURN + #define UNITY_NORETURN UNITY_FUNCTION_ATTR(__noreturn__) + #endif #endif /*------------------------------------------------------- @@ -180,6 +228,8 @@ #define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* #endif +/* optionally define UNITY_COMPARE_PTRS_ON_ZERO_ARRAY */ + /*------------------------------------------------------- * Float Support *-------------------------------------------------------*/ @@ -205,16 +255,25 @@ #endif typedef UNITY_FLOAT_TYPE UNITY_FLOAT; -/* isinf & isnan macros should be provided by math.h */ -#ifndef isinf -/* The value of Inf - Inf is NaN */ -#define isinf(n) (isnan((n) - (n)) && !isnan(n)) -#endif - +/* isnan macro should be provided by math.h. Override if not macro */ +#ifndef UNITY_IS_NAN #ifndef isnan /* NaN is the only floating point value that does NOT equal itself. * Therefore if n != n, then it is NaN. */ -#define isnan(n) ((n != n) ? 1 : 0) +#define UNITY_IS_NAN(n) ((n != n) ? 1 : 0) +#else +#define UNITY_IS_NAN(n) isnan(n) +#endif +#endif + +/* isinf macro should be provided by math.h. Override if not macro */ +#ifndef UNITY_IS_INF +#ifndef isinf +/* The value of Inf - Inf is NaN */ +#define UNITY_IS_INF(n) (UNITY_IS_NAN((n) - (n)) && !UNITY_IS_NAN(n)) +#else +#define UNITY_IS_INF(n) isinf(n) +#endif #endif #endif @@ -273,10 +332,10 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT; #ifdef UNITY_USE_FLUSH_STDOUT /* We want to use the stdout flush utility */ #include - #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) + #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) #else /* We've specified nothing, therefore flush should just be ignored */ - #define UNITY_OUTPUT_FLUSH() + #define UNITY_OUTPUT_FLUSH() (void)0 #endif #else /* If defined as something else, make sure we declare it here so it's ready for use */ @@ -288,11 +347,11 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT; #ifndef UNITY_OUTPUT_FLUSH #define UNITY_FLUSH_CALL() #else -#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() +#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() #endif #ifndef UNITY_PRINT_EOL -#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') +#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') #endif #ifndef UNITY_OUTPUT_START @@ -333,7 +392,7 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT; UnityPrintNumberUnsigned(execTimeMs); \ UnityPrint(" ms)"); \ } - #elif defined(__unix__) + #elif defined(__unix__) || defined(__APPLE__) #include #define UNITY_TIME_TYPE struct timespec #define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t) @@ -351,19 +410,19 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT; #endif #ifndef UNITY_EXEC_TIME_START -#define UNITY_EXEC_TIME_START() do{}while(0) +#define UNITY_EXEC_TIME_START() do { /* nothing*/ } while (0) #endif #ifndef UNITY_EXEC_TIME_STOP -#define UNITY_EXEC_TIME_STOP() do{}while(0) +#define UNITY_EXEC_TIME_STOP() do { /* nothing*/ } while (0) #endif #ifndef UNITY_TIME_TYPE -#define UNITY_TIME_TYPE UNITY_UINT +#define UNITY_TIME_TYPE UNITY_UINT #endif #ifndef UNITY_PRINT_EXEC_TIME -#define UNITY_PRINT_EXEC_TIME() do{}while(0) +#define UNITY_PRINT_EXEC_TIME() do { /* nothing*/ } while (0) #endif /*------------------------------------------------------- @@ -502,9 +561,9 @@ void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int #define UNITY_SET_DETAIL(d1) #define UNITY_SET_DETAILS(d1,d2) #else -#define UNITY_CLR_DETAILS() { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } -#define UNITY_SET_DETAIL(d1) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } -#define UNITY_SET_DETAILS(d1,d2) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } +#define UNITY_CLR_DETAILS() do { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAIL(d1) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } while (0) +#define UNITY_SET_DETAILS(d1,d2) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } while (0) #ifndef UNITY_DETAIL1_NAME #define UNITY_DETAIL1_NAME "Function" @@ -618,8 +677,8 @@ void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, const UNITY_FLAGS_T flags); #ifndef UNITY_EXCLUDE_SETJMP_H -void UnityFail(const char* message, const UNITY_LINE_TYPE line) UNITY_FUNCTION_ATTR(noreturn); -void UnityIgnore(const char* message, const UNITY_LINE_TYPE line) UNITY_FUNCTION_ATTR(noreturn); +UNITY_NORETURN void UnityFail(const char* message, const UNITY_LINE_TYPE line); +UNITY_NORETURN void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); #else void UnityFail(const char* message, const UNITY_LINE_TYPE line); void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); @@ -634,12 +693,25 @@ void UnityAssertFloatsWithin(const UNITY_FLOAT delta, const char* msg, const UNITY_LINE_TYPE lineNumber); -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, +void UnityAssertFloatsNotWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertGreaterOrLessFloat(const UNITY_FLOAT threshold, + const UNITY_FLOAT actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE linenumber); + +void UnityAssertWithinFloatArray(const UNITY_FLOAT delta, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); void UnityAssertFloatSpecial(const UNITY_FLOAT actual, const char* msg, @@ -654,12 +726,25 @@ void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, const char* msg, const UNITY_LINE_TYPE lineNumber); -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, +void UnityAssertDoublesNotWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertGreaterOrLessDouble(const UNITY_DOUBLE threshold, + const UNITY_DOUBLE actual, + const UNITY_COMPARISON_T compare, + const char* msg, + const UNITY_LINE_TYPE linenumber); + +void UnityAssertWithinDoubleArray(const UNITY_DOUBLE delta, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, const char* msg, @@ -697,27 +782,58 @@ extern const char UnityStrErrShorthand[]; * Test Running Macros *-------------------------------------------------------*/ +#ifdef UNITY_TEST_PROTECT +#define TEST_PROTECT() UNITY_TEST_PROTECT() +#else #ifndef UNITY_EXCLUDE_SETJMP_H #define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) -#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) #else #define TEST_PROTECT() 1 +#endif +#endif + +#ifdef UNITY_TEST_ABORT +#define TEST_ABORT() UNITY_TEST_ABORT() +#else +#ifndef UNITY_EXCLUDE_SETJMP_H +#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) +#else #define TEST_ABORT() return #endif +#endif + +/* Automatically enable variadic macros support, if it not enabled before */ +#ifndef UNITY_SUPPORT_VARIADIC_MACROS + #ifdef __STDC_VERSION__ + #if __STDC_VERSION__ >= 199901L + #define UNITY_SUPPORT_VARIADIC_MACROS + #endif + #endif +#endif /* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ #ifndef RUN_TEST -#ifdef __STDC_VERSION__ -#if __STDC_VERSION__ >= 199901L -#define UNITY_SUPPORT_VARIADIC_MACROS -#endif -#endif #ifdef UNITY_SUPPORT_VARIADIC_MACROS #define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway) #define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line) #endif #endif +/* Enable default macros for masking param tests test cases */ +#ifdef UNITY_SUPPORT_TEST_CASES + #ifdef UNITY_SUPPORT_VARIADIC_MACROS + #if !defined(TEST_CASE) && !defined(UNITY_EXCLUDE_TEST_CASE) + #define TEST_CASE(...) + #endif + #if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE) + #define TEST_RANGE(...) + #endif + #if !defined(TEST_MATRIX) && !defined(UNITY_EXCLUDE_TEST_MATRIX) + #define TEST_MATRIX(...) + #endif + #endif +#endif + /* If we can't do the tricky version, we'll just have to require them to always include the line number */ #ifndef RUN_TEST #ifdef CMOCK @@ -772,11 +888,11 @@ int UnityTestMatches(void); * Test Asserts *-------------------------------------------------------*/ -#define UNITY_TEST_ASSERT(condition, line, message) do {if (condition) {} else {UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message));}} while(0) -#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT(condition, line, message) do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((line), (message)); } } while (0) +#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (line), (message)) +#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (line), (message)) +#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (line), (message)) +#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (line), (message)) #define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) #define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) @@ -875,7 +991,7 @@ int UnityTestMatches(void); #define UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) #define UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) #define UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) #define UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) #define UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) #define UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) @@ -981,9 +1097,16 @@ int UnityTestMatches(void); #ifdef UNITY_EXCLUDE_FLOAT #define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) @@ -994,9 +1117,16 @@ int UnityTestMatches(void); #define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) #else #define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsNotWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) #define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray((UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray(UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_NOT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_NOT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_FLOAT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)(delta), (const UNITY_FLOAT*)(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, (const UNITY_FLOAT*)(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertWithinFloatArray((UNITY_FLOAT)0, UnityFloatToPtr(expected), (const UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_GREATER_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_THAN_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_FLOAT(threshold, actual, line, message) UnityAssertGreaterOrLessFloat((UNITY_FLOAT)(threshold), (UNITY_FLOAT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) #define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) #define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) #define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) @@ -1009,9 +1139,16 @@ int UnityTestMatches(void); #ifdef UNITY_EXCLUDE_DOUBLE #define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) @@ -1022,9 +1159,16 @@ int UnityTestMatches(void); #define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) #else #define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesNotWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) #define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray((UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray(UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_NOT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_NOT_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_DOUBLE_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)(delta), (const UNITY_DOUBLE*)(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, (const UNITY_DOUBLE*)(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertWithinDoubleArray((UNITY_DOUBLE)0, UnityDoubleToPtr(expected), (const UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_GREATER_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_THAN_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_LESS_OR_EQUAL_DOUBLE(threshold, actual, line, message) UnityAssertGreaterOrLessDouble((UNITY_DOUBLE)(threshold), (UNITY_DOUBLE)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line)) #define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) #define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) #define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) diff --git a/tests/validation/test_parameter_validation.cpp b/tests/validation/test_parameter_validation.cpp index ce16ce4..64e1b8e 100644 --- a/tests/validation/test_parameter_validation.cpp +++ b/tests/validation/test_parameter_validation.cpp @@ -13,7 +13,7 @@ void test_handle_syntax_error() { std::string result_json_expected = get_json_sample(TEST_SYNTAX_ERROR_RESULT_JSON); - racfu_result_t *result = racfu(request_json, false); + racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); } @@ -23,7 +23,7 @@ void test_handle_syntax_error_not_json() { std::string result_json_expected = get_json_sample(TEST_SYNTAX_ERROR_NOT_JSON_RESULT_JSON); - racfu_result_t *result = racfu(request_json, false); + racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); } @@ -34,7 +34,7 @@ void test_handle_syntax_error_binary_data() { std::string result_json_expected = get_json_sample(TEST_SYNTAX_ERROR_BINARY_DATA_RESULT_JSON); - racfu_result_t *result = racfu(request_json, false); + racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); } From 61c4809783d23aa20154575db7cae05c1c0d06a2 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Fri, 25 Apr 2025 16:12:57 -0400 Subject: [PATCH 20/32] Fuzzing fixes. Signed-off-by: Leonard Carcaramo --- Makefile | 9 ++-- README.md | 4 +- racfu/irrseq00/profile_extractor.cpp | 53 +++++++++++++++-------- racfu/irrseq00/profile_extractor.hpp | 3 +- racfu/irrseq00/profile_post_processor.cpp | 4 -- racfu/security_request.cpp | 6 +-- {fuzz => tests}/fuzz.cpp | 0 7 files changed, 46 insertions(+), 33 deletions(-) rename {fuzz => tests}/fuzz.cpp (100%) diff --git a/Makefile b/Makefile index 4eaaa4b..1458416 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ schema: racfu: clean mkdirs schema $(AS) $(ASFLAGS) -o $(ARTIFACTS)/irrseq00.o $(IRRSEQ00_SRC)/irrseq00.s cd $(ARTIFACTS) \ - && $(CXX) -c $(CFLAGS) \ + && $(CXX) -g -c $(CFLAGS) \ $(SRC)/*.cpp \ $(IRRSMO00_SRC)/*.cpp \ $(IRRSEQ00_SRC)/*.cpp \ @@ -106,7 +106,7 @@ racfu: clean mkdirs schema test: clean mkdirs schema cd $(ARTIFACTS) \ - && $(CXX) -c $(CFLAGS) $(TFLAGS) \ + && $(CXX) -g -c $(CFLAGS) $(TFLAGS) \ $(TESTS)/unity/unity.c \ $(TESTS)/mock/*.cpp \ $(SRCZOSLIB) \ @@ -125,8 +125,8 @@ test: clean mkdirs schema fuzz: clean mkdirs schema cd $(ARTIFACTS) \ - && $(CXX) -c $(CFLAGS) $(TFLAGS) $(FUZZFLGS) \ - ${PWD}/fuzz/fuzz.cpp \ + && $(CXX) -g -c $(CFLAGS) $(TFLAGS) $(FUZZFLGS) \ + $(TESTS)/fuzz.cpp \ $(TESTS)/mock/*.cpp \ $(SRCZOSLIB) \ $(SRC)/*.cpp \ @@ -136,6 +136,7 @@ fuzz: clean mkdirs schema $(VALIDATION)/*.cpp \ $(JSON_SCHEMA)/*.cpp \ && $(CXX) $(LDFLAGS) $(FUZZFLGS) *.o -o $(DIST)/fuzz + ASAN_OPTIONS=alloc_dealloc_mismatch=1 \ $(DIST)/fuzz -runs=65536 -artifact_prefix=$(ARTIFACTS)/ fvt: diff --git a/README.md b/README.md index 872720f..4a1e30a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ![RACFu Logo](https://raw.githubusercontent.com/ambitus/racfu/refs/heads/main/logo.png) [![test](https://github.com/ambitus/racfu/actions/workflows/test.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/test.yml) +[![fuzz](https://github.com/ambitus/racfu/actions/workflows/fuzz.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/fuzz.yml) [![cppcheck](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/cppcheck.yml) [![clang-format](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml) -[fuzz](https://github.com/ambitus/racfu/actions/workflows/clang-format.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/fuzz.yml) [![ruff](https://github.com/ambitus/racfu/actions/workflows/ruff.yml/badge.svg)](https://github.com/ambitus/racfu/actions/workflows/ruff.yml) [![Version](https://img.shields.io/pypi/v/racfu?label=alpha)](https://pypi.org/project/racfu/#history) [![Python Versions](https://img.shields.io/pypi/pyversions/racfu)](https://pypi.org/project/racfu/) @@ -17,7 +17,7 @@ As automation becomes more and more prevalent, the need to manage the security e While there are a number of languages that can be used to manage RACF, _(from low level lnaguages like Assembler to higher level languages like REXX)_, the need to be able to easily exploit RACF management functions using existing indurstry standard programming languages and even programming languages that don't exist yet is paramount. The RACFu project is focused on making RACF management functions available to all programming languages that have native JSON support and a foreign language interface for C/C++. This will make it easier to pivot to new tools and programming languages as technology, skills, and business needs continue to evolve in the forseeable future. -RACfu uses [JSON for Modern C++](https://github.com/nlohmann/json) for working with JSON, and [JSON schema validator for JSON for Modern C++](https://github.com/pboettch/json-schema-validator) for [Draft-7 JSON Schema Validation](https://json-schema.org/draft-07). +RACFu uses [JSON for Modern C++](https://github.com/nlohmann/json) for working with JSON and [JSON schema validator for JSON for Modern C++](https://github.com/pboettch/json-schema-validator) for [Draft-7 JSON Schema Validation](https://json-schema.org/draft-07). ## Getting Started diff --git a/racfu/irrseq00/profile_extractor.cpp b/racfu/irrseq00/profile_extractor.cpp index bec8546..90dcb09 100644 --- a/racfu/irrseq00/profile_extractor.cpp +++ b/racfu/irrseq00/profile_extractor.cpp @@ -44,7 +44,7 @@ void ProfileExtractor::extract(SecurityRequest &request) { Logger::getInstance().hexDump(reinterpret_cast(p_arg_area), request.getRawRequestLength()); - request.setRawRequestPointer(ProfileExtractor::preserveRawRequest( + request.setRawRequestPointer(ProfileExtractor::cloneBuffer( reinterpret_cast(p_arg_area), request.getRawRequestLength())); // Call R_Admin @@ -82,7 +82,7 @@ void ProfileExtractor::extract(SecurityRequest &request) { Logger::getInstance().hexDump(reinterpret_cast(p_arg_area), request.getRawRequestLength()); - request.setRawRequestPointer(ProfileExtractor::preserveRawRequest( + request.setRawRequestPointer(ProfileExtractor::cloneBuffer( reinterpret_cast(p_arg_area), request.getRawRequestLength())); // Call R_Admin @@ -117,6 +117,31 @@ void ProfileExtractor::extract(SecurityRequest &request) { << static_cast(request.getRawResultPointer()); Logger::getInstance().debug(oss.str()); + // We need to create a new buffer for the raw result to ensure + // that it is allocated using "new" since the cleanup done later + // will assume that the buffer was allocated using "new" and + // will free the buffer using "delete". Since the 31-bit buffer + // is allocated by irrseq00.s using '__malloc31()', we can't free + // it using "delete" since that will result in undefined behavior. + // Instead just create a new buffer using "make_unique()", which is + // allocated using "new" under the covers, and free the original + // buffer using "free()"". + char *p_raw_result = request.getRawResultPointer(); + int raw_result_length; + if (request.getAdminType() != "racf-options") { + const generic_extract_parms_results_t *p_generic_result = + reinterpret_cast(p_raw_result); + raw_result_length = ntohl(p_generic_result->result_buffer_length); + } else { + const racf_options_extract_results_t *p_setropts_result = + reinterpret_cast(p_raw_result); + raw_result_length = ntohl(p_setropts_result->result_buffer_length); + } + request.setRawResultPointer( + ProfileExtractor::cloneBuffer(p_raw_result, raw_result_length)); + std::free(p_raw_result); + request.setRawResultLength(raw_result_length); + request.setRACFuReturnCode(0); } @@ -240,21 +265,13 @@ void ProfileExtractor::buildRACFOptionsExtractRequest( arg_pointers->p_racf_options_extract_parms = racf_options_extract_parms; } -char *ProfileExtractor::preserveRawRequest(const char *p_arg_area, - const int &raw_request_length) { - try { - auto request_unique_ptr = std::make_unique(raw_request_length); - Logger::getInstance().debugAllocate(request_unique_ptr.get(), 64, - raw_request_length); - std::memset(request_unique_ptr.get(), 0, raw_request_length); - std::memcpy(request_unique_ptr.get(), p_arg_area, raw_request_length); - char *p_raw_request = request_unique_ptr.get(); - request_unique_ptr.release(); - return p_raw_request; - } catch (const std::bad_alloc &ex) { - std::perror( - "Warn - Unable to allocate space to preserve the raw request.\n"); - return nullptr; - } +char *ProfileExtractor::cloneBuffer(const char *p_buffer, const int &length) { + auto request_unique_ptr = std::make_unique(length); + Logger::getInstance().debugAllocate(request_unique_ptr.get(), 64, length); + std::memset(request_unique_ptr.get(), 0, length); + std::memcpy(request_unique_ptr.get(), p_buffer, length); + char *p_raw_request = request_unique_ptr.get(); + request_unique_ptr.release(); + return p_raw_request; } } // namespace RACFu diff --git a/racfu/irrseq00/profile_extractor.hpp b/racfu/irrseq00/profile_extractor.hpp index e8064bd..1d4dcf2 100644 --- a/racfu/irrseq00/profile_extractor.hpp +++ b/racfu/irrseq00/profile_extractor.hpp @@ -18,8 +18,7 @@ class ProfileExtractor { std::string class_name, uint8_t function_code); static void buildRACFOptionsExtractRequest( racf_options_extract_underbar_arg_area_t *arg_area); - static char *preserveRawRequest(const char *p_arg_area, - const int &raw_request_length); + static char *cloneBuffer(const char *p_buffer, const int &length); public: void extract(SecurityRequest &request); diff --git a/racfu/irrseq00/profile_post_processor.cpp b/racfu/irrseq00/profile_post_processor.cpp index 204b4ac..135d4bd 100644 --- a/racfu/irrseq00/profile_post_processor.cpp +++ b/racfu/irrseq00/profile_post_processor.cpp @@ -33,7 +33,6 @@ void ProfilePostProcessor::postProcessGeneric(SecurityRequest &request) { const char *p_profile = request.getRawResultPointer(); const generic_extract_parms_results_t *p_generic_result = reinterpret_cast(p_profile); - request.setRawResultLength(ntohl(p_generic_result->result_buffer_length)); Logger::getInstance().debug("Raw generic profile extract result:"); Logger::getInstance().hexDump(p_profile, request.getRawResultLength()); @@ -112,9 +111,6 @@ void ProfilePostProcessor::postProcessRACFOptions(SecurityRequest &request) { // Profile Pointers and Information const char *p_profile = request.getRawResultPointer(); - const racf_options_extract_results_t *p_setropts_result = - reinterpret_cast(p_profile); - request.setRawResultLength(ntohl(p_setropts_result->result_buffer_length)); Logger::getInstance().debug("Raw RACF Options extract result:"); Logger::getInstance().hexDump(p_profile, request.getRawResultLength()); diff --git a/racfu/security_request.cpp b/racfu/security_request.cpp index 4966bee..14544f4 100644 --- a/racfu/security_request.cpp +++ b/racfu/security_request.cpp @@ -21,17 +21,17 @@ SecurityRequest::SecurityRequest(racfu_result_t* p_result) { // Free dynamically allocated memory from previous requests. if (p_result->raw_request != nullptr) { Logger::getInstance().debugFree(p_result->raw_request); - std::free(p_result->raw_request); + delete[] p_result->raw_request; Logger::getInstance().debug("Done"); } if (p_result->raw_result != nullptr) { Logger::getInstance().debugFree(p_result->raw_result); - std::free(p_result->raw_result); + delete[] p_result->raw_result; Logger::getInstance().debug("Done"); } if (p_result->result_json != nullptr) { Logger::getInstance().debugFree(p_result->result_json); - std::free(p_result->result_json); + delete[] p_result->result_json; Logger::getInstance().debug("Done"); } p_result_->raw_request = nullptr; diff --git a/fuzz/fuzz.cpp b/tests/fuzz.cpp similarity index 100% rename from fuzz/fuzz.cpp rename to tests/fuzz.cpp From 0001d5db50aa4420a5dbb81aff1133868cebd76c Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:06:05 +0200 Subject: [PATCH 21/32] Fix DCO --- past_commits.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 past_commits.txt diff --git a/past_commits.txt b/past_commits.txt new file mode 100644 index 0000000..6420d54 --- /dev/null +++ b/past_commits.txt @@ -0,0 +1,4 @@ +The following commits were made pursuant to the Developer Certificate of Origin, even though a Signed-off-by: was not included in the commit message. + +<297f7f5f720dabadc077fd28d59ea1a00f1c7d1b> +... \ No newline at end of file From 216ad8e0dd9afff33e65ae08189e2672aee28d7e Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:11:05 +0200 Subject: [PATCH 22/32] Removed template leftover --- past_commits.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/past_commits.txt b/past_commits.txt index 6420d54..ba5f3af 100644 --- a/past_commits.txt +++ b/past_commits.txt @@ -1,4 +1,4 @@ The following commits were made pursuant to the Developer Certificate of Origin, even though a Signed-off-by: was not included in the commit message. -<297f7f5f720dabadc077fd28d59ea1a00f1c7d1b> +297f7f5f720dabadc077fd28d59ea1a00f1c7d1b Fixed type issues ... \ No newline at end of file From e8e491d829b4a89a70e7c8a3dfa45b14e36b3949 Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:55:18 +0200 Subject: [PATCH 23/32] fix past_commits.txt Signed-off-by: Emma S. <64806882+EmmasBox@users.noreply.github.com> --- past_commits.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/past_commits.txt b/past_commits.txt index ba5f3af..ecf11fb 100644 --- a/past_commits.txt +++ b/past_commits.txt @@ -1,4 +1,3 @@ The following commits were made pursuant to the Developer Certificate of Origin, even though a Signed-off-by: was not included in the commit message. -297f7f5f720dabadc077fd28d59ea1a00f1c7d1b Fixed type issues -... \ No newline at end of file +297f7f5f720dabadc077fd28d59ea1a00f1c7d1b Fixed type issues \ No newline at end of file From 84b51db9890f1919f9e85bcce64a82fd45641358 Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:33:29 +0200 Subject: [PATCH 24/32] Fixed DCO (finally) Signed-off-by: Emma S. <64806882+EmmasBox@users.noreply.github.com> --- past_commits.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/past_commits.txt b/past_commits.txt index ecf11fb..a465065 100644 --- a/past_commits.txt +++ b/past_commits.txt @@ -1,3 +1,5 @@ The following commits were made pursuant to the Developer Certificate of Origin, even though a Signed-off-by: was not included in the commit message. +216ad8e0dd9afff33e65ae08189e2672aee28d7e Removed template leftover +0001d5db50aa4420a5dbb81aff1133868cebd76c Fix DCO 297f7f5f720dabadc077fd28d59ea1a00f1c7d1b Fixed type issues \ No newline at end of file From e96793a3342833f42f515c60ca7af045603ba204 Mon Sep 17 00:00:00 2001 From: "Emma S." <64806882+EmmasBox@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:33:44 +0200 Subject: [PATCH 25/32] Configured Ruff for more aggressive linting and excluded folders without python code Signed-off-by: Emma S. <64806882+EmmasBox@users.noreply.github.com> --- pyproject.toml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e9e0fe5..1221ee6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,5 +50,37 @@ build-backend = "setuptools.build_meta" where = ["python"] include = ["racfu"] +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".git", + ".github", + "externals", + "racfu", + "debug", + "dev_tools", + ] + +[tool.ruff.lint] +# Ruff by default is not very aggressive, only enforcing a few rulesets like E and F. +# To ensure well organized code a bunch more rulesets have been enabled +select = [ + "ERA", #Checks for commented out code + "E", + "F", + "B", + "EM", #Error messages and exceptions + "SIM", #Comes with suggestions on simplifying things + "N", #Checks if code conforms to the PEP8 naming conventions + "PLE", + "UP", + "TRY", #Best practices for try-except + "FLY", #Checks for string joins + "I", #Comes with suggestions on organizing imports + "COM", #Checks for missing commas and unnecessary + "A", + ] + + [tool.setuptools.package-data] racfu = ["LICENSE", "NOTICES"] From 5cadfc33bd793178c54dbf12a533f455cc8bda4f Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Wed, 30 Apr 2025 14:57:44 -0400 Subject: [PATCH 26/32] Resolve clang-format issues and Add ZOSLIB and OpenSSL to build. Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 43 ++++++++++++++++++++++++++++++++ Makefile | 6 ++--- racfu/irrseq00/irrseq00.hpp | 2 +- racfu/irrsmo00/xml_parser.cpp | 3 ++- racfu/irrsmo00/xml_parser.hpp | 2 +- tests/irrsdl00/test_irrsdl00.hpp | 6 ++--- tests/test_runner.cpp | 2 +- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2423dd5..6ea0cf5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,6 +51,10 @@ pipeline { ansiColor('css') } + environment { + ZOPEN_ROOTFS = "${env.WORKSPACE}/zopen/zopen" + } + stages { stage('Parameter Validation') { steps { @@ -74,6 +78,45 @@ pipeline { clean_python_environment() } } + stage('Get ZOSLIB and OpenSSL from zopen community') { + steps { + script { + zoslib_build_id = "20250425_143120" + openssl_version = "3.4.0" + openssl_build_id = "20250123_021310" + + sh "mkdir zopen" + + dir('zopen') { + echo "Installing zoslib ..." + sh """ + curl -o zoslib-zopen2.${zoslib_build_id}.zos.pax.Z \\ + -L https://github.com/zopencommunity/zoslibport/releases/download/STABLE_zoslibport_3261/zoslib-zopen2.${zoslib_build_id}.zos.pax.Z + pax -rf zoslib-zopen2.${zoslib_build_id}.zos.pax.Z + """ + echo "Installing OpenSSL" + sh """ + curl -o openssl-${openssl_version}.${openssl_build_id}.zos.pax.Z \\ + -L https://github.com/zopencommunity/opensslport/releases/download/STABLE_opensslport_2838/openssl-${openssl_version}.${openssl_build_id}.zos.pax.Z + pax -rf openssl-${openssl_version}.${openssl_build_id}.zos.pax.Z + """ + echo "Creating symbolic links ..." + sh """ + mkdir -p zopen/usr/local/lib + mkdir -p zopen/usr/local/include + ln -s ${env.WORKSPACE}/zopen/zoslib-zopen2/lib/libzoslib.a \\ + ${env.WORKSPACE}/zopen/zopen/usr/local/lib/libzoslib.a + ln -s ${env.WORKSPACE}/zopen/openssl-${openssl_version}/lib/libcrypto.a \\ + ${env.WORKSPACE}/zopen/zopen/usr/local/lib/libcrypto.a + ln -s ${env.WORKSPACE}/zopen/openssl-${openssl_version}/lib/libssl.a \\ + ${env.WORKSPACE}/zopen/zopen/usr/local/lib/libssl.a + ln -s ${env.WORKSPACE}/zopen/openssl-${openssl_version}/include/openssl \\ + ${env.WORKSPACE}/zopen/zopen/usr/local/include/openssl + """ + } + } + } + } stage('Cppcheck') { steps { echo "Running cppcheck ..." diff --git a/Makefile b/Makefile index fb7af01..6fc48ec 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ VALIDATION = ${PWD}/racfu/validation EXTERNALS = ${PWD}/externals JSON = $(EXTERNALS)/json JSON_SCHEMA = $(EXTERNALS)/json-schema-validator -OPENSSL = ${ZOPEN_ROOTFS}/usr/local/include TESTS = ${PWD}/tests ZOSLIB = $(TESTS)/zoslib @@ -49,9 +48,10 @@ ifeq ($(UNAME), OS/390) ASFLAGS = -mGOFF -I$(IRRSEQ00_SRC) CFLAGS = \ - -std=$(CXXSTANDARD) -m64 -fzos-le-char-mode=ascii + -std=$(CXXSTANDARD) -m64 -fzos-le-char-mode=ascii \ -D_POSIX_C_SOURCE=200112L \ - $(COMMON_INC) + -I ${ZOPEN_ROOTFS}/usr/local/include \ + $(COMMONv_INC) TFLAGS = \ -DUNIT_TEST -DUNITY_OUTPUT_COLOR \ -I ${PWD} \ diff --git a/racfu/irrseq00/irrseq00.hpp b/racfu/irrseq00/irrseq00.hpp index 7472bd8..faee164 100644 --- a/racfu/irrseq00/irrseq00.hpp +++ b/racfu/irrseq00/irrseq00.hpp @@ -83,7 +83,7 @@ typedef struct { uint32_t ACEE; uint8_t result_buffer_subpool; // R_admin returns data here - char * __ptr32 p_result_buffer; + char *__ptr32 p_result_buffer; } generic_extract_args_t; typedef struct { diff --git a/racfu/irrsmo00/xml_parser.cpp b/racfu/irrsmo00/xml_parser.cpp index 4dc8ee2..5475197 100644 --- a/racfu/irrsmo00/xml_parser.cpp +++ b/racfu/irrsmo00/xml_parser.cpp @@ -172,7 +172,8 @@ std::string XMLParser::replaceXMLChars(std::string xml_data) { return xml_data; } -std::string XMLParser::replaceSubstring(std::string data, std::string &substring, +std::string XMLParser::replaceSubstring(std::string data, + std::string& substring, std::string replacement, std::size_t start) { std::size_t match; diff --git a/racfu/irrsmo00/xml_parser.hpp b/racfu/irrsmo00/xml_parser.hpp index 22c7023..63127dd 100644 --- a/racfu/irrsmo00/xml_parser.hpp +++ b/racfu/irrsmo00/xml_parser.hpp @@ -18,7 +18,7 @@ class XMLParser { static void updateJSON(nlohmann::json& input_json, nlohmann::json& inner_data, std::string outer_tag); static std::string replaceXMLChars(std::string xml_data); - static std::string replaceSubstring(std::string data, std::string &substring, + static std::string replaceSubstring(std::string data, std::string& substring, std::string replacement, std::size_t start); diff --git a/tests/irrsdl00/test_irrsdl00.hpp b/tests/irrsdl00/test_irrsdl00.hpp index a0e8f07..c481a73 100644 --- a/tests/irrsdl00/test_irrsdl00.hpp +++ b/tests/irrsdl00/test_irrsdl00.hpp @@ -14,7 +14,7 @@ IRRSDL00_REQUEST_SAMPLES "keyring/test_extract_keyring_request.bin" #define TEST_EXTRACT_KEYRING_REQUEST_KEYRING_NOT_FOUND_JSON \ IRRSDL00_REQUEST_SAMPLES \ - "keyring/test_extract_keyring_request_keyring_not_found.json" + "keyring/test_extract_keyring_request_keyring_not_found.json" #define TEST_EXTRACT_KEYRING_REQUEST_REQUIRED_PARAMETER_MISSING_JSON \ IRRSDL00_REQUEST_SAMPLES \ "keyring/test_extract_keyring_request_required_parameter_missing.json" @@ -29,10 +29,10 @@ IRRSDL00_RESULT_SAMPLES "keyring/test_extract_keyring_result.bin" #define TEST_EXTRACT_KEYRING_RESULT_KEYRING_NOT_FOUND_JSON \ IRRSDL00_RESULT_SAMPLES \ - "keyring/test_extract_keyring_result_keyring_not_found.json" + "keyring/test_extract_keyring_result_keyring_not_found.json" #define TEST_EXTRACT_KEYRING_RESULT_KEYRING_NOT_FOUND_RAW \ IRRSDL00_RESULT_SAMPLES \ - "keyring/test_extract_keyring_result_keyring_not_found.bin" + "keyring/test_extract_keyring_result_keyring_not_found.bin" /*************************************************************************/ /* Prototypes */ diff --git a/tests/test_runner.cpp b/tests/test_runner.cpp index 8113975..f7f8027 100644 --- a/tests/test_runner.cpp +++ b/tests/test_runner.cpp @@ -1,7 +1,7 @@ #include "irrseq00.hpp" +#include "tests/irrsdl00/test_irrsdl00.hpp" #include "tests/irrseq00/test_irrseq00.hpp" #include "tests/irrsmo00/test_irrsmo00.hpp" -#include "tests/irrsdl00/test_irrsdl00.hpp" #include "tests/unity/unity.h" #include "tests/validation/test_parameter_validation.hpp" From 91b55dfedc744be10e49cde637ce9d74237b6071 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Wed, 30 Apr 2025 16:24:48 -0400 Subject: [PATCH 27/32] Debug build Signed-off-by: Leonard Carcaramo --- Jenkinsfile | 6 +++--- Makefile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6ea0cf5..d183c24 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -86,7 +86,7 @@ pipeline { openssl_build_id = "20250123_021310" sh "mkdir zopen" - + dir('zopen') { echo "Installing zoslib ..." sh """ @@ -120,13 +120,13 @@ pipeline { stage('Cppcheck') { steps { echo "Running cppcheck ..." - sh "make check" + sh "gmake check" } } stage('Unit Test') { steps { echo "Running unit tests ..." - sh "make test" + sh "gmake test" } } stage('Create Python Distribution Metadata') { diff --git a/Makefile b/Makefile index 6fc48ec..85ffb49 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ ifeq ($(UNAME), OS/390) -std=$(CXXSTANDARD) -m64 -fzos-le-char-mode=ascii \ -D_POSIX_C_SOURCE=200112L \ -I ${ZOPEN_ROOTFS}/usr/local/include \ - $(COMMONv_INC) + $(COMMON_INC) TFLAGS = \ -DUNIT_TEST -DUNITY_OUTPUT_COLOR \ -I ${PWD} \ From e3026f54b9c10eca8fff4959ad8466cfa0323436 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 1 May 2025 09:59:06 -0400 Subject: [PATCH 28/32] Debug build & return length with for result JSON. Signed-off-by: Leonard Carcaramo --- .github/PULL_REQUEST_TEMPLATE.md | 2 ++ Jenkinsfile | 2 +- README.md | 2 ++ racfu/python/_racfu.c | 5 +++-- racfu/racfu.cpp | 4 ++++ racfu/racfu_result.h | 1 + racfu/security_request.cpp | 13 ++++++++----- tests/irrsmo00/test_irrsmo00.cpp | 3 ++- tests/unit_test_utilities.cpp | 15 ++++++++++----- 9 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b5e5c77..0c7ac4a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,5 @@ +⚠️ _Ensure all of your commits have a [Developer Certification of Origin (DCO) signoff](https://github.com/openmainframeproject/tsc/blob/master/process/contribution_guidelines.md#developer-certificate-of-origin). This is very easy to overlook and can be done retroactively if you forgot to do so by either adding the commits that don't have a DCO signoff to `past_commits.txt` or squashing commits that are missing a DCO signoff into one commit that does have a DCO signoff._ + ### :bulb: Issue Reference **Issue:** *Include any issues related to this PR* diff --git a/Jenkinsfile b/Jenkinsfile index d183c24..a4f92bf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -276,7 +276,7 @@ def publish( def tar_published = false echo "Cleaning repo ..." - sh "git clean -fdx" + sh "git clean -fdx '!zopen'" for (python in python_executables_and_wheels_map.keySet()) { def wheel_default = python_executables_and_wheels_map[python]["wheelDefault"] diff --git a/README.md b/README.md index 4a1e30a..d277efa 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ While there are a number of languages that can be used to manage RACF, _(from lo RACFu uses [JSON for Modern C++](https://github.com/nlohmann/json) for working with JSON and [JSON schema validator for JSON for Modern C++](https://github.com/pboettch/json-schema-validator) for [Draft-7 JSON Schema Validation](https://json-schema.org/draft-07). +RACFu's certificate management cababilities leverage the [zopen community](https://zopen.community/#/) distributions of [OpenSSL](https://github.com/openssl/openssl) and [ZOSLIB](https://github.com/ibmruntimes/zoslib) to create checksum digests and encode certificates. + ## Getting Started diff --git a/racfu/python/_racfu.c b/racfu/python/_racfu.c index ed5078d..0a04127 100644 --- a/racfu/python/_racfu.c +++ b/racfu/python/_racfu.c @@ -35,9 +35,10 @@ static PyObject* call_racfu(PyObject* self, PyObject* args, PyObject* kwargs) { racfu_result_t* result = racfu(request_as_string, request_length, debug); result_dictionary = Py_BuildValue( - "{s:y#,s:y#,s:s}", "raw_request", result->raw_request, + "{s:y#,s:y#,s:s#}", "raw_request", result->raw_request, result->raw_request_length, "raw_result", result->raw_result, - result->raw_result_length, "result_json", result->result_json); + result->raw_result_length, "result_json", result->result_json, + result->result_json_length); pthread_mutex_unlock(&racfu_mutex); diff --git a/racfu/racfu.cpp b/racfu/racfu.cpp index ed352f9..15a2519 100644 --- a/racfu/racfu.cpp +++ b/racfu/racfu.cpp @@ -1,7 +1,11 @@ #include "racfu.h" +#include + #include "security_admin.hpp" +pthread_mutex_t racfu_mutex = PTHREAD_MUTEX_INITIALIZER; + racfu_result_t *racfu(const char *request_json, int length, bool debug) { static racfu_result_t racfu_result = {nullptr, 0, nullptr, 0, nullptr}; RACFu::SecurityAdmin security_admin = diff --git a/racfu/racfu_result.h b/racfu/racfu_result.h index 5f13faa..41bd47e 100644 --- a/racfu/racfu_result.h +++ b/racfu/racfu_result.h @@ -7,6 +7,7 @@ typedef struct { char *raw_result; int raw_result_length; char *result_json; + int result_json_length; } racfu_result_t; typedef struct { diff --git a/racfu/security_request.cpp b/racfu/security_request.cpp index d419504..2895cf7 100644 --- a/racfu/security_request.cpp +++ b/racfu/security_request.cpp @@ -286,12 +286,15 @@ void SecurityRequest::buildResult() { Logger::getInstance().debug("Result JSON:", result_json_string); try { auto result_json_unique_ptr = - std::make_unique(result_json_string.size() + 1); + std::make_unique(result_json_string.length() + 1); Logger::getInstance().debugAllocate(result_json_unique_ptr.get(), 64, - result_json_string.size() + 1); - std::memset(result_json_unique_ptr.get(), 0, result_json_string.size() + 1); - std::strcpy(result_json_unique_ptr.get(), result_json_string.c_str()); - p_result_->result_json = result_json_unique_ptr.get(); + result_json_string.length() + 1); + std::memset(result_json_unique_ptr.get(), 0, + result_json_string.length() + 1); + std::strncpy(result_json_unique_ptr.get(), result_json_string.c_str(), + result_json_string.length()); + p_result_->result_json = result_json_unique_ptr.get(); + p_result_->result_json_length = result_json_string.length(); result_json_unique_ptr.release(); Logger::getInstance().debug("Done"); } catch (const std::bad_alloc& ex) { diff --git a/tests/irrsmo00/test_irrsmo00.cpp b/tests/irrsmo00/test_irrsmo00.cpp index c8ea846..7a45332 100644 --- a/tests/irrsmo00/test_irrsmo00.cpp +++ b/tests/irrsmo00/test_irrsmo00.cpp @@ -128,7 +128,8 @@ void test_parse_irrsmo00_errors_result() { TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); // Cleanup free(irrsmo64_result_mock); diff --git a/tests/unit_test_utilities.cpp b/tests/unit_test_utilities.cpp index c77e869..b7ab93c 100644 --- a/tests/unit_test_utilities.cpp +++ b/tests/unit_test_utilities.cpp @@ -168,7 +168,8 @@ void test_parse_extract_result(const char *test_extract_request_json, TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); TEST_ASSERT_EQUAL_INT32(r_admin_result_size_mock, result->raw_result_length); // Cleanup @@ -198,7 +199,8 @@ void test_parse_extract_result_profile_not_found( TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); } void check_arg_pointers(char *raw_request, bool racf_options) { @@ -355,7 +357,8 @@ void test_parse_add_alter_delete_result( TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); // Cleanup free(irrsmo64_result_mock); @@ -414,7 +417,8 @@ void test_parse_extract_irrsdl00_result(const char *test_extract_request_json, TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); TEST_ASSERT_EQUAL_INT32(irrsdl64_result_size_mock, result->raw_result_length); // Cleanup @@ -443,5 +447,6 @@ void test_parse_extract_irrsdl00_result_keyring_not_found( TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), - strlen(result->result_json)); + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); } From 583314dcac82811a03cc7eb54c4c434703c1992c Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 1 May 2025 10:16:17 -0400 Subject: [PATCH 29/32] Remove pythread from racfu.cpp Signed-off-by: Leonard Carcaramo --- racfu/racfu.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/racfu/racfu.cpp b/racfu/racfu.cpp index 15a2519..ed352f9 100644 --- a/racfu/racfu.cpp +++ b/racfu/racfu.cpp @@ -1,11 +1,7 @@ #include "racfu.h" -#include - #include "security_admin.hpp" -pthread_mutex_t racfu_mutex = PTHREAD_MUTEX_INITIALIZER; - racfu_result_t *racfu(const char *request_json, int length, bool debug) { static racfu_result_t racfu_result = {nullptr, 0, nullptr, 0, nullptr}; RACFu::SecurityAdmin security_admin = From 268b8172bce5b755cdb433d81ca031f4e974617b Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 1 May 2025 10:19:52 -0400 Subject: [PATCH 30/32] Cleanup. Signed-off-by: Leonard Carcaramo --- racfu/python/_racfu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/racfu/python/_racfu.c b/racfu/python/_racfu.c index 0a04127..92f1f78 100644 --- a/racfu/python/_racfu.c +++ b/racfu/python/_racfu.c @@ -48,14 +48,15 @@ static PyObject* call_racfu(PyObject* self, PyObject* args, PyObject* kwargs) { // Method definition static PyMethodDef _C_methods[] = { {"call_racfu", (PyCFunction)call_racfu, METH_VARARGS | METH_KEYWORDS, - "Python interface to RACF administration APIs"}, + "A unified and standardized interface to RACF callable services"}, {NULL} }; // Module definition static struct PyModuleDef _C_module_def = { - PyModuleDef_HEAD_INIT, "_C", "Python interface to RACF administration APIs", - -1, _C_methods}; + PyModuleDef_HEAD_INIT, "_C", + "A unified and standardized interface to RACF callable services", -1, + _C_methods}; // Module initialization function // 'unusedFunction' is a false positive since 'PyInit__C()' is used by the From e24b65f180527c445601c68b313a9ba3b4a2eb3d Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 1 May 2025 11:09:33 -0400 Subject: [PATCH 31/32] Cleanup Signed-off-by: Leonard Carcaramo --- .pre-commit-config.yaml | 8 -------- CONTRIBUTING.md | 3 ++- tests/validation/test_parameter_validation.cpp | 9 +++++++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 822526a..368709b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,14 +13,6 @@ repos: pass_filenames: false always_run: true verbose: true - - repo: local - hooks: - - id: fuzz - name: fuzz - entry: make fuzz - language: system - pass_filenames: false - always_run: true - repo: local hooks: - id: unit-test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a22c2d..fd03807 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,8 +138,9 @@ When contributing to RACFu, think about the following: * Add any necessary test cases to `/tests`. * Ensure that you have **pre-commit Hooks** setup to ensure that `clang-format`, `cppcheck`, and **unit tests** are run against the code for every commit you make. * Run unit tests by running `make test`. +* Run LLVM LibFuzzer by running `make fuzz`. * Run functional verification tests by running `make fvt`. -* Run `cppcheck` static code analysis cans by running `make check` +* Run `cppcheck` static code analysis cans by running `make check`. ## Found a bug? diff --git a/tests/validation/test_parameter_validation.cpp b/tests/validation/test_parameter_validation.cpp index 64e1b8e..182b534 100644 --- a/tests/validation/test_parameter_validation.cpp +++ b/tests/validation/test_parameter_validation.cpp @@ -16,6 +16,9 @@ void test_handle_syntax_error() { racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); + TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); } void test_handle_syntax_error_not_json() { @@ -26,6 +29,9 @@ void test_handle_syntax_error_not_json() { racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); + TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); } void test_handle_syntax_error_binary_data() { @@ -37,6 +43,9 @@ void test_handle_syntax_error_binary_data() { racfu_result_t *result = racfu(request_json, strlen(request_json), false); TEST_ASSERT_EQUAL_STRING(result_json_expected.c_str(), result->result_json); + TEST_ASSERT_EQUAL_INT32(result_json_expected.length(), + result->result_json_length); + TEST_ASSERT_EQUAL_CHAR(0, result->result_json[result->result_json_length]); } void test_parse_no_parameters_provided_error() { From d373519ec038df782efc85680d0168e08a94e82f Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 1 May 2025 11:24:07 -0400 Subject: [PATCH 32/32] Fix ZOPEN_ROOTFS check. Signed-off-by: Leonard Carcaramo --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index fab90e4..51ac994 100644 --- a/setup.py +++ b/setup.py @@ -71,12 +71,12 @@ def run(self): def main(): """Python extension build entrypoint.""" cwd = Path.cwd() - # Use ZOPEN_ROOTFS to find OpenSSL and zoslib. - if not os.environ["ZOPEN_ROOTFS"]: + # Use ZOPEN_ROOTFS to find OpenSSL and ZOSLIB. + if "ZOPEN_ROOTFS" not in os.environ: raise RuntimeError( - "ZOPEN_ROOTFS not is not set, but is required in order to " + "ZOPEN_ROOTFS is not set, but is required in order to " + "find the zopen community distributions of of OpenSSL " - + "and zoslib since they are build dependencies.\n" + + "and ZOSLIB since they are build dependencies.\n" + "You can find more information about setting up zopen " + "community here: " + "https://zopen.community/#/Guides/QuickStart?id=installing-zopen-package-manager",