mirror of
https://github.com/nlohmann/json.git
synced 2026-11-12 15:10:47 +00:00
Compare commits
127
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e55a6a08a | ||
|
|
b6070980a1 | ||
|
|
7fc8312c64 | ||
|
|
18138227dc | ||
|
|
951271127b | ||
|
|
cbb9e14889 | ||
|
|
b54267d2a7 | ||
|
|
e48800f09d | ||
|
|
20fe34ceeb | ||
|
|
1da8735154 | ||
|
|
a49242f088 | ||
|
|
42825fba07 | ||
|
|
efc437ef6e | ||
|
|
57b898ef99 | ||
|
|
92707f43f2 | ||
|
|
e32051125f | ||
|
|
baba5db36f | ||
|
|
07876e2292 | ||
|
|
b18b6fc7e6 | ||
|
|
6d5581c4e8 | ||
|
|
590b622d48 | ||
|
|
100f676b3e | ||
|
|
bd067fd08c | ||
|
|
3354436ec4 | ||
|
|
596028c460 | ||
|
|
5c4f585ac2 | ||
|
|
1d69ef9ab4 | ||
|
|
ff0bdfdef3 | ||
|
|
7328310136 | ||
|
|
bfe69c73c2 | ||
|
|
494a74c242 | ||
|
|
bdc737db6d | ||
|
|
66e2eeeec0 | ||
|
|
03d408aafa | ||
|
|
76dbe8f9c9 | ||
|
|
405b8dcb13 | ||
|
|
02d2fa8605 | ||
|
|
b2674a4a2f | ||
|
|
e2423a1857 | ||
|
|
67b103e506 | ||
|
|
ddc34f546a | ||
|
|
f022e3c59c | ||
|
|
9742edc294 | ||
|
|
517444b504 | ||
|
|
fb515ddc34 | ||
|
|
edf8d17601 | ||
|
|
aecbef3949 | ||
|
|
6c3ab966d3 | ||
|
|
5337d841d4 | ||
|
|
162eda2574 | ||
|
|
1a018577cc | ||
|
|
76001c6b4c | ||
|
|
ec6986c918 | ||
|
|
3fd3e0fdaa | ||
|
|
9c349b1d7c | ||
|
|
367963f6bb | ||
|
|
4fe670b5f4 | ||
|
|
755bb16d7a | ||
|
|
d20c4dcd89 | ||
|
|
ea03d601af | ||
|
|
e2cb15c479 | ||
|
|
c451fac1bc | ||
|
|
28f71914b5 | ||
|
|
0b91f9ddd8 | ||
|
|
0115bb47b3 | ||
|
|
ea8a05484b | ||
|
|
2aecd86b37 | ||
|
|
5eccbbd2bc | ||
|
|
46bead6e13 | ||
|
|
54f6b3ef06 | ||
|
|
091c10d4a5 | ||
|
|
2120927a7a | ||
|
|
282079ba82 | ||
|
|
da83aa92c8 | ||
|
|
84c5421644 | ||
|
|
52e9f098eb | ||
|
|
a6c5d970f3 | ||
|
|
d9c0fda829 | ||
|
|
a763c9eb83 | ||
|
|
5c2aba70f9 | ||
|
|
510ee0ab4f | ||
|
|
5da0e9181f | ||
|
|
0aff24ca37 | ||
|
|
c25aad3ca5 | ||
|
|
6a0cf2ba2f | ||
|
|
24de5b2f5c | ||
|
|
fb729028ba | ||
|
|
c9fdfc39db | ||
|
|
870bdec01f | ||
|
|
effa51d528 | ||
|
|
545477ffa8 | ||
|
|
aeba637593 | ||
|
|
466e56d92d | ||
|
|
b7b75aac8a | ||
|
|
516b9b516a | ||
|
|
1d2dea7769 | ||
|
|
f8ff0942b2 | ||
|
|
d1e6439187 | ||
|
|
dcf7e2222c | ||
|
|
d99932fb3f | ||
|
|
1d2cb49ff3 | ||
|
|
a2766df787 | ||
|
|
2ea5bed0e3 | ||
|
|
632f5369fe | ||
|
|
c2619bf460 | ||
|
|
351aeb7440 | ||
|
|
7b0af0072e | ||
|
|
650886d126 | ||
|
|
a759afc99f | ||
|
|
1143da4faa | ||
|
|
8de151f928 | ||
|
|
ab52c98f71 | ||
|
|
5e93415d91 | ||
|
|
7e8e8e219b | ||
|
|
5f1727cef2 | ||
|
|
c16dd7e4f5 | ||
|
|
792853d725 | ||
|
|
4bdf1b7e74 | ||
|
|
b1e9d98e41 | ||
|
|
f855d257df | ||
|
|
3e683e9c04 | ||
|
|
d1d84ed9af | ||
|
|
de8529f99b | ||
|
|
677794f076 | ||
|
|
437a95cfdb | ||
|
|
c8735246d0 | ||
|
|
9adb510a0d |
No files matched your search
@@ -111,6 +111,8 @@ context which existing file needs to be extended, and only very few cases requir
|
||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||
Tests that call `sax_parse` go into `unit-sax_parse.cpp` rather than into a large test file: every call instantiates
|
||||
the parser and the binary reader that recover from errors, which grows a test file considerably.
|
||||
|
||||
#### Exceptions
|
||||
|
||||
@@ -154,15 +156,6 @@ and each page's title (H1) and declaration by
|
||||
[`docs/docset/generate_docset.py`](https://github.com/nlohmann/json/blob/develop/docs/docset/generate_docset.py);
|
||||
`make build` reports API pages that cannot be classified.
|
||||
|
||||
A pull request that changes only the documentation, Markdown files, GitHub metadata, or license files skips the
|
||||
compiler workflows (Ubuntu, macOS, Windows); the documentation and license checks still run. See
|
||||
[`tools/check_path_filters`](https://github.com/nlohmann/json/tree/develop/tools/check_path_filters) for the list of
|
||||
skipped files and what to do when a compiler job starts reading one of them.
|
||||
|
||||
Most compiler jobs run only once a maintainer adds the label "🚀 ready to merge" to a pull request; until then, a
|
||||
smaller set of jobs runs on every push. See
|
||||
[How CI runs](https://json.nlohmann.me/community/quality_assurance/#how-ci-runs) for the sets and when they run.
|
||||
|
||||
### Amalgamate the source code
|
||||
|
||||
The single-header files
|
||||
|
||||
@@ -24,11 +24,11 @@ jobs:
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ permissions:
|
||||
jobs:
|
||||
save:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ concurrency:
|
||||
jobs:
|
||||
check_docs_links:
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
comment:
|
||||
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ on:
|
||||
jobs:
|
||||
flawfinder:
|
||||
name: Flawfinder
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -48,6 +48,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -7,60 +7,25 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# labeled: the "🚀 ready to merge" label starts the full set (see below)
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/macos.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
# A run triggered by any other label gets a group of its own, so it does not
|
||||
# cancel the run of the last push; its jobs are skipped.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && 'nightly' || github.ref || github.run_id }}${{ github.event.action == 'labeled' && github.event.label.name != '🚀 ready to merge' && format('-{0}', github.run_id) || '' }}
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
# The jobs are split into a PR set and a full set like in ubuntu.yml (see "How
|
||||
# CI runs" in docs/mkdocs/docs/community/quality_assurance.md): the PR set runs
|
||||
# on every push; the full set after the PR set passed, on pushes and on pull
|
||||
# requests with the "🚀 ready to merge" label. tools/ci_jobs checks that every
|
||||
# job belongs to exactly one set.
|
||||
|
||||
jobs:
|
||||
###########################################################################
|
||||
# PR set
|
||||
###########################################################################
|
||||
|
||||
macos-15:
|
||||
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['16.0', '16.4', '26.3']
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
steps: &xcode_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -72,11 +37,10 @@ jobs:
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
macos-26:
|
||||
if: *pr_set
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['26.6']
|
||||
xcode: ['26.4.1', '26.5', '26.6']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
@@ -92,13 +56,12 @@ jobs:
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
xcode_standards:
|
||||
if: *pr_set
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [14]
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
|
||||
steps: &xcode_standards_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -110,7 +73,6 @@ jobs:
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
swiftpm:
|
||||
if: *pr_set
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -144,42 +106,3 @@ jobs:
|
||||
EOF
|
||||
cd /tmp/json-swiftpm-consumer
|
||||
swift build
|
||||
|
||||
|
||||
###########################################################################
|
||||
# full set: starts after the PR set passed
|
||||
###########################################################################
|
||||
|
||||
macos-15_full:
|
||||
needs: &pr_set_jobs [macos-15, macos-26, xcode_standards, swiftpm]
|
||||
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['16.1', '16.2', '16.3', '26.0.1', '26.1.1', '26.2']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
steps: *xcode_steps
|
||||
|
||||
macos-26_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['26.4.1', '26.5']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
steps: *xcode_steps
|
||||
|
||||
xcode_standards_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 17, 20, 23, 26]
|
||||
|
||||
steps: *xcode_standards_steps
|
||||
@@ -34,10 +34,10 @@ jobs:
|
||||
contents: write
|
||||
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
name: Repository checks
|
||||
|
||||
# Checks that concern files the compiler workflows (ubuntu.yml, macos.yml,
|
||||
# windows.yml) skip with their path filter: the documentation, the license
|
||||
# information, and the CI configuration itself. This workflow has no path filter.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check_ci_configuration:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check the path filters of the compiler workflows
|
||||
run: python3 tools/check_path_filters/check_path_filters.py
|
||||
- name: Check the job sets of the compiler workflows and their documentation
|
||||
run: |
|
||||
python3 -m venv venv
|
||||
venv/bin/pip install --quiet -r tools/ci_jobs/requirements.txt
|
||||
venv/bin/python tools/ci_jobs/ci_jobs.py
|
||||
|
||||
ci_reuse_compliance:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_reuse_compliance
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail
|
||||
fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }}
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
if: matrix.target == 'ci_test_documentation_mermaid'
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
@@ -24,7 +24,7 @@ permissions:
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -8,7 +8,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
+251
-411
@@ -7,84 +7,38 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# labeled: the "🚀 ready to merge" label starts the full set (see below)
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/ubuntu.yml'
|
||||
schedule:
|
||||
# the nightly set (see below), on develop
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# A run triggered by any other label gets a group of its own, so it does not
|
||||
# cancel the run of the last push; its jobs are skipped.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && 'nightly' || github.ref || github.run_id }}${{ github.event.action == 'labeled' && github.event.label.name != '🚀 ready to merge' && format('-{0}', github.run_id) || '' }}
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# The jobs are split into three sets (see "How CI runs" in
|
||||
# docs/mkdocs/docs/community/quality_assurance.md):
|
||||
#
|
||||
# - PR set: every pull request push and every push; chosen because these jobs
|
||||
# caught 92 % of the failing commits in Sep/Oct 2026.
|
||||
# - full set: after the PR set passed, on pushes and on pull requests with the
|
||||
# "🚀 ready to merge" label; every remaining compiler, version and standard.
|
||||
# - nightly set: slow tools and rare compilers, nightly on develop, on pushes
|
||||
# to master and release/*, and when started by hand.
|
||||
#
|
||||
# A matrix whose entries belong to different sets is split into one job per
|
||||
# set; the jobs share their steps through YAML anchors. tools/ci_jobs checks
|
||||
# that every job belongs to exactly one set.
|
||||
|
||||
jobs:
|
||||
###########################################################################
|
||||
# PR set
|
||||
###########################################################################
|
||||
|
||||
ci_test_gcc:
|
||||
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: gcc:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Check that Meson and pkg-config offer the CMake options
|
||||
run: make check_build_options
|
||||
- name: Install Meson
|
||||
@@ -122,15 +76,43 @@ jobs:
|
||||
cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_infer:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install Infer
|
||||
run: |
|
||||
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
||||
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_infer
|
||||
- name: Archive Infer report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: infer-report
|
||||
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cpplint]
|
||||
steps: &static_analysis_ubuntu_steps
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -141,40 +123,38 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_static_analysis_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_single_binaries]
|
||||
steps: &static_analysis_clang_steps
|
||||
target: [ci_test_clang, ci_clang_tidy, ci_test_clang_sanitizer, ci_clang_analyze, ci_single_binaries]
|
||||
steps:
|
||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_cmake_options:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noglobaludls]
|
||||
steps: &cmake_options_steps
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
||||
# the CMake 3.5.0 release binary that ci_cmake_flags downloads links
|
||||
@@ -188,318 +168,17 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8']
|
||||
# official gcc:4.8/4.9/5/6 images fail to check out code (too old for
|
||||
# actions/checkout); install the old compilers on top of official ubuntu:20.04
|
||||
# instead, mirroring what the (now retired) custom json-ci image did.
|
||||
container: ubuntu:20.04
|
||||
steps: &compilers_gcc_old_steps
|
||||
- name: Install g++-${{ matrix.compiler }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||
# add-apt-repository resolves the PPA through the Launchpad API,
|
||||
# which intermittently times out or fails the team lookup (the plain
|
||||
# "deb ..." sources below never hit Launchpad and never flake).
|
||||
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
||||
for attempt in 1 2 3 4 5; do
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
||||
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps: &compilers_clang_steps
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
|
||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [26]
|
||||
steps: &standards_gcc_steps
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [26]
|
||||
stdlib: [libcxx]
|
||||
steps: &standards_clang_steps
|
||||
- name: Install git and unzip
|
||||
run: apt-get update ; apt-get install -y git unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build with libc++
|
||||
run: cmake --build build --target ci_test_clang_libcxx_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libcxx' }}
|
||||
- name: Build with libstdc++
|
||||
run: cmake --build build --target ci_test_clang_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_emscripten:
|
||||
if: *pr_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install emscripten
|
||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
###########################################################################
|
||||
# full set: starts after the PR set passed
|
||||
###########################################################################
|
||||
|
||||
ci_static_analysis_ubuntu_full:
|
||||
needs: &pr_set_jobs [ci_test_gcc, ci_meson_install, ci_static_analysis_ubuntu, ci_static_analysis_clang, ci_cmake_options, ci_test_compilers_gcc_old, ci_test_compilers_clang, ci_test_standards_gcc, ci_test_standards_clang, ci_emscripten]
|
||||
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata]
|
||||
steps: *static_analysis_ubuntu_steps
|
||||
|
||||
ci_static_analysis_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_clang_analyze]
|
||||
steps: *static_analysis_clang_steps
|
||||
|
||||
ci_cmake_options_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
|
||||
steps: *cmake_options_steps
|
||||
|
||||
ci_test_compilers_gcc_old_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.9', '5', '6']
|
||||
container: ubuntu:20.04
|
||||
steps: *compilers_gcc_old_steps
|
||||
|
||||
ci_test_compilers_gcc_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps: *compilers_clang_steps
|
||||
|
||||
ci_test_standards_gcc_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23]
|
||||
steps: *standards_gcc_steps
|
||||
|
||||
ci_test_standards_clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
stdlib: [libcxx, libstdcxx]
|
||||
exclude:
|
||||
# in the PR set
|
||||
- standard: 26
|
||||
stdlib: libcxx
|
||||
steps: *standards_clang_steps
|
||||
|
||||
ci_module_cpp20:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||
# ships none), and the image's libc++ module manifest has a broken relative
|
||||
# path — repoint it at the real module sources.
|
||||
- name: Use libc++ and fix its module manifest path (Clang)
|
||||
if: matrix.container == 'silkeh/clang:latest'
|
||||
run: |
|
||||
echo "CXXFLAGS=-stdlib=libc++" >> "$GITHUB_ENV"
|
||||
mkdir -p /usr/lib/share && ln -sf /usr/lib/llvm-*/share/libc++ /usr/lib/share/libc++
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
###########################################################################
|
||||
# nightly set
|
||||
###########################################################################
|
||||
|
||||
ci_infer:
|
||||
if: &nightly_set ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref != 'refs/heads/develop') }}
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install Infer
|
||||
run: |
|
||||
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
||||
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_infer
|
||||
- name: Archive Infer report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: infer-report
|
||||
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu_nightly:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_valgrind]
|
||||
steps: *static_analysis_ubuntu_steps
|
||||
|
||||
ci_test_coverage:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -529,9 +208,129 @@ jobs:
|
||||
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
||||
fail-on-error: false
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8', '4.9', '5', '6']
|
||||
# official gcc:4.8/4.9/5/6 images fail to check out code (too old for
|
||||
# actions/checkout); install the old compilers on top of official ubuntu:20.04
|
||||
# instead, mirroring what the (now retired) custom json-ci image did.
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- name: Install g++-${{ matrix.compiler }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||
# add-apt-repository resolves the PPA through the Launchpad API,
|
||||
# which intermittently times out or fails the team lookup (the plain
|
||||
# "deb ..." sources below never hit Launchpad and never flake).
|
||||
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
||||
for attempt in 1 2 3 4 5; do
|
||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
||||
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial universe"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates main"
|
||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_gcc:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||
container: gcc:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
container: silkeh/clang:${{ matrix.compiler }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
|
||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
runs-on: ubuntu-26.04
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
runs-on: ubuntu-26.04
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
standard: [11, 14, 17, 20, 23, 26]
|
||||
stdlib: [libcxx, libstdcxx]
|
||||
steps:
|
||||
- name: Install git and unzip
|
||||
run: apt-get update ; apt-get install -y git unzip
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build with libc++
|
||||
run: cmake --build build --target ci_test_clang_libcxx_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libcxx' }}
|
||||
- name: Build with libstdc++
|
||||
run: cmake --build build --target ci_test_clang_cxx${{ matrix.standard }}
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_cuda_example:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -547,15 +346,44 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_cuda_example
|
||||
|
||||
ci_module_cpp20:
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-26.04
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||
# ships none), and the image's libc++ module manifest has a broken relative
|
||||
# path — repoint it at the real module sources.
|
||||
- name: Use libc++ and fix its module manifest path (Clang)
|
||||
if: matrix.container == 'silkeh/clang:latest'
|
||||
run: |
|
||||
echo "CXXFLAGS=-stdlib=libc++" >> "$GITHUB_ENV"
|
||||
mkdir -p /usr/lib/share && ln -sf /usr/lib/llvm-*/share/libc++ /usr/lib/share/libc++
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
ci_icpc:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||
# Intel's own last officially published image that still includes it.
|
||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||
@@ -564,7 +392,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -574,39 +402,81 @@ jobs:
|
||||
run: cmake --build build --target ci_icpc
|
||||
|
||||
ci_icpx:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: intel/oneapi-hpckit:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_icpx
|
||||
|
||||
ci_nvhpc:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_nvhpc
|
||||
|
||||
ci_emscripten:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Install emscripten
|
||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||
- name: Build
|
||||
run: cmake --build build
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail
|
||||
fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }}
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
if: matrix.target == 'ci_test_documentation_mermaid'
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
# Native Linux ARM64 runner with the latest GCC. The ubuntu-24.04-arm label is
|
||||
# only available for public repositories.
|
||||
ci_test_arm64:
|
||||
if: *nightly_set
|
||||
runs-on: ubuntu-24.04-arm
|
||||
container: gcc:latest
|
||||
steps:
|
||||
@@ -614,40 +484,10 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Check that the job runs natively on arm64
|
||||
run: uname -m && test "$(uname -m)" = aarch64
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
# A failure of a scheduled run opens an issue, or comments on the open one.
|
||||
nightly_report:
|
||||
needs: [ci_infer, ci_static_analysis_ubuntu_nightly, ci_test_coverage, ci_cuda_example, ci_icpc, ci_icpx, ci_nvhpc, ci_test_arm64]
|
||||
if: ${{ always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Open or update the nightly failure issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
WORKFLOW: ${{ github.workflow }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
title="Nightly CI failure (${WORKFLOW})"
|
||||
body="The nightly run of ${WORKFLOW} on ${SHA:0:10} failed: ${RUN_URL}"
|
||||
number=$(gh issue list --state open --search "in:title \"${title}\"" --json number,title --jq "map(select(.title == \"${title}\")) | .[0].number // empty")
|
||||
if [ -n "$number" ]; then
|
||||
gh issue comment "$number" --body "$body"
|
||||
else
|
||||
gh issue create --title "$title" --body "$body"
|
||||
fi
|
||||
+36
-218
@@ -7,67 +7,28 @@ on:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
# labeled: the "🚀 ready to merge" label starts the full set (see below)
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
# Skip pull requests that change only files no job here reads. The list is checked by
|
||||
# tools/check_path_filters/check_path_filters.py (repository_checks.yml); change both together.
|
||||
paths:
|
||||
- '**'
|
||||
- '!docs/**'
|
||||
- '!**.md'
|
||||
- '!.github/*'
|
||||
- '!.github/ISSUE_TEMPLATE/**'
|
||||
- '!.github/external_ci/**'
|
||||
- '!.github/workflows/**'
|
||||
- '!.reuse/**'
|
||||
- '!LICENSES/**'
|
||||
- '!CITATION.cff'
|
||||
- '!REUSE.toml'
|
||||
# read by tools/check_build_options in the ci_meson_install job (ubuntu.yml)
|
||||
- 'docs/mkdocs/docs/integration/package_managers.md'
|
||||
# built by the swiftpm job (macos.yml)
|
||||
- 'docs/mkdocs/docs/integration/swift/**'
|
||||
- '.github/workflows/windows.yml'
|
||||
schedule:
|
||||
# the nightly set (see below), on develop
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# A run triggered by any other label gets a group of its own, so it does not
|
||||
# cancel the run of the last push; its jobs are skipped.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && 'nightly' || github.ref || github.run_id }}${{ github.event.action == 'labeled' && github.event.label.name != '🚀 ready to merge' && format('-{0}', github.run_id) || '' }}
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
# The jobs are split into three sets like in ubuntu.yml (see "How CI runs" in
|
||||
# docs/mkdocs/docs/community/quality_assurance.md): the PR set runs on every
|
||||
# push; the full set after the PR set passed, on pushes and on pull requests
|
||||
# with the "🚀 ready to merge" label; the nightly set nightly on develop, on
|
||||
# pushes to master and release/*, and when started by hand. tools/ci_jobs
|
||||
# checks that every job belongs to exactly one set.
|
||||
|
||||
jobs:
|
||||
###########################################################################
|
||||
# PR set
|
||||
###########################################################################
|
||||
|
||||
mingw:
|
||||
if: &pr_set ${{ github.event_name != 'schedule' && (github.event.action != 'labeled' || github.event.label.name == '🚀 ready to merge') }}
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [x86]
|
||||
architecture: [x64, x86]
|
||||
|
||||
steps: &mingw_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up MinGW
|
||||
uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0
|
||||
uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0
|
||||
with:
|
||||
platform: ${{ matrix.architecture }}
|
||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||
@@ -79,26 +40,15 @@ jobs:
|
||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||
|
||||
msvc:
|
||||
if: *pr_set
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build_type: Debug
|
||||
architecture: x64
|
||||
std_version: default
|
||||
- build_type: Release
|
||||
architecture: x64
|
||||
std_version: default
|
||||
- build_type: Debug
|
||||
architecture: Win32
|
||||
std_version: default
|
||||
- build_type: Release
|
||||
architecture: Win32
|
||||
std_version: latest
|
||||
build_type: [Debug, Release]
|
||||
architecture: [Win32, x64]
|
||||
std_version: [default, latest]
|
||||
|
||||
runs-on: windows-2022
|
||||
|
||||
steps: &msvc_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -127,25 +77,20 @@ jobs:
|
||||
# Visual Studio 2026 (v145 toolset) on the windows-2025 image. The "Visual Studio
|
||||
# 18 2026" generator requires CMake 4.2+, so a recent CMake is fetched explicitly.
|
||||
msvc-vs2026:
|
||||
if: *pr_set
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- build_type: Debug
|
||||
architecture: x64
|
||||
std_version: latest
|
||||
- build_type: Release
|
||||
architecture: Win32
|
||||
std_version: default
|
||||
build_type: [Debug, Release]
|
||||
architecture: [Win32, x64]
|
||||
std_version: [default, latest]
|
||||
|
||||
runs-on: windows-2025
|
||||
|
||||
steps: &msvc_vs2026_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
- name: Set extra CXX_FLAGS for latest std_version
|
||||
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
||||
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
||||
@@ -172,14 +117,13 @@ jobs:
|
||||
# Native ARM64 Windows runner with the MSVC ARM64 toolset. The windows-11-arm
|
||||
# label is only available for public repositories.
|
||||
msvc-arm64:
|
||||
if: *pr_set
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Debug]
|
||||
build_type: [Debug, Release]
|
||||
|
||||
runs-on: windows-11-arm
|
||||
|
||||
steps: &msvc_arm64_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -197,20 +141,19 @@ jobs:
|
||||
run: cd build ; ctest -j 10 -C ${{ matrix.build_type }} --output-on-failure
|
||||
|
||||
clang:
|
||||
if: *pr_set
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
version: [20.1.8]
|
||||
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7, 20.1.8]
|
||||
|
||||
steps: &clang_steps
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install Clang
|
||||
run: curl -fsSL -o LLVM${{ matrix.version }}.exe https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.version }}/LLVM-${{ matrix.version }}-win64.exe ; 7z x LLVM${{ matrix.version }}.exe -y -o"C:/Program Files/LLVM"
|
||||
- name: Set up MinGW
|
||||
uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0
|
||||
uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0
|
||||
with:
|
||||
platform: x64
|
||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||
@@ -246,109 +189,7 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||
|
||||
ci_module_cpp20:
|
||||
if: *pr_set
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and
|
||||
# ci_module_cpp20 forwards it to the module build, so list /EHsc explicitly
|
||||
- name: Run CMake (Debug)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DJSON_CI=ON -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /EHsc /W4 /WX"
|
||||
- name: Build
|
||||
run: cmake --build build --config Debug --target ci_module_cpp20
|
||||
|
||||
|
||||
|
||||
###########################################################################
|
||||
# full set: starts after the PR set passed
|
||||
###########################################################################
|
||||
|
||||
msvc_full:
|
||||
needs: &pr_set_jobs [mingw, msvc, msvc-vs2026, msvc-arm64, clang, ci_module_cpp20]
|
||||
if: &full_set ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, '🚀 ready to merge') }}
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
architecture: [Win32, x64]
|
||||
std_version: [default, latest]
|
||||
exclude:
|
||||
# in the PR set
|
||||
- build_type: Debug
|
||||
architecture: x64
|
||||
std_version: default
|
||||
- build_type: Release
|
||||
architecture: x64
|
||||
std_version: default
|
||||
- build_type: Debug
|
||||
architecture: Win32
|
||||
std_version: default
|
||||
- build_type: Release
|
||||
architecture: Win32
|
||||
std_version: latest
|
||||
|
||||
runs-on: windows-2022
|
||||
|
||||
steps: *msvc_steps
|
||||
|
||||
# Visual Studio 2026 (v145 toolset) on the windows-2025 image. The "Visual Studio
|
||||
# 18 2026" generator requires CMake 4.2+, so a recent CMake is fetched explicitly.
|
||||
msvc-vs2026_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Debug, Release]
|
||||
architecture: [Win32, x64]
|
||||
std_version: [default, latest]
|
||||
exclude:
|
||||
# in the PR set
|
||||
- build_type: Debug
|
||||
architecture: x64
|
||||
std_version: latest
|
||||
- build_type: Release
|
||||
architecture: Win32
|
||||
std_version: default
|
||||
|
||||
runs-on: windows-2025
|
||||
|
||||
steps: *msvc_vs2026_steps
|
||||
|
||||
# Native ARM64 Windows runner with the MSVC ARM64 toolset. The windows-11-arm
|
||||
# label is only available for public repositories.
|
||||
msvc-arm64_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: [Release]
|
||||
|
||||
runs-on: windows-11-arm
|
||||
|
||||
steps: *msvc_arm64_steps
|
||||
|
||||
clang_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7]
|
||||
|
||||
steps: *clang_steps
|
||||
|
||||
clang-cl_full:
|
||||
needs: *pr_set_jobs
|
||||
if: *full_set
|
||||
clang-cl:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -365,46 +206,23 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
||||
|
||||
|
||||
###########################################################################
|
||||
# nightly set
|
||||
###########################################################################
|
||||
|
||||
mingw_nightly:
|
||||
if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref != 'refs/heads/develop') }}
|
||||
ci_module_cpp20:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
matrix:
|
||||
architecture: [x64]
|
||||
|
||||
steps: *mingw_steps
|
||||
|
||||
# A failure of a scheduled run opens an issue, or comments on the open one.
|
||||
nightly_report:
|
||||
needs: [mingw_nightly]
|
||||
if: ${{ always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure') }}
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Open or update the nightly failure issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
WORKFLOW: ${{ github.workflow }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
title="Nightly CI failure (${WORKFLOW})"
|
||||
body="The nightly run of ${WORKFLOW} on ${SHA:0:10} failed: ${RUN_URL}"
|
||||
number=$(gh issue list --state open --search "in:title \"${title}\"" --json number,title --jq "map(select(.title == \"${title}\")) | .[0].number // empty")
|
||||
if [ -n "$number" ]; then
|
||||
gh issue comment "$number" --body "$body"
|
||||
else
|
||||
gh issue create --title "$title" --body "$body"
|
||||
fi
|
||||
persist-credentials: false
|
||||
# The module test uses `import std;`, which needs CMake's experimental
|
||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@f86dd60acae0d132056913e248bdabf0e0580b7a # v4.4.5
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and
|
||||
# ci_module_cpp20 forwards it to the module build, so list /EHsc explicitly
|
||||
- name: Run CMake (Debug)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DJSON_CI=ON -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /EHsc /W4 /WX"
|
||||
- name: Build
|
||||
run: cmake --build build --config Debug --target ci_module_cpp20
|
||||
|
||||
@@ -495,7 +495,7 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
@@ -503,7 +503,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
|
||||
### STL-like access
|
||||
|
||||
@@ -1408,9 +1408,9 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
||||
- The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, including the conversion of the digits to text by Xiang JunBo and the SIMD instruction sequence of Dougall Johnson, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2025 [Victor Zverovich](https://github.com/vitaut)
|
||||
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
- The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||
- The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
- The class's lexer and the view's parser (`<nlohmann/json_view.hpp>`) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
|
||||
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
||||
|
||||
|
||||
+3
-3
@@ -348,20 +348,20 @@ add_custom_target(ci_test_no_thread_local
|
||||
|
||||
add_custom_target(ci_test_coverage
|
||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage"
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||
-DJSON_BuildTests=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage"
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||
-DJSON_BuildTests=ON -DJSON_32bitTest=ONLY
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||
|
||||
+3
-1
@@ -178,8 +178,10 @@ function(_json_test_add_test test_name file main cxx_standard)
|
||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
# under Valgrind the slowest tests take close to 25 minutes, so ctest's
|
||||
# default timeout of 1500 seconds leaves no headroom
|
||||
set_tests_properties(${test_target}_valgrind PROPERTIES
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA TIMEOUT 3600
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -120,6 +120,16 @@ overload (3).
|
||||
See [Number conversion](../../features/types/number_handling.md#number-conversion)
|
||||
for more information.
|
||||
|
||||
!!! note "Booleans as numbers"
|
||||
|
||||
A JSON boolean converts to `#!cpp 0` or `#!cpp 1` for every arithmetic type **except** [`number_integer_t`](number_integer_t.md),
|
||||
[`number_unsigned_t`](number_unsigned_t.md) and [`number_float_t`](number_float_t.md) (by default `#!cpp std::int64_t`,
|
||||
`#!cpp std::uint64_t`, and `#!cpp double`), for which `#!cpp get<T>()` throws
|
||||
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) ("type must be number, but is boolean"). So
|
||||
`#!cpp json(true).get<int>()` is `#!cpp 1`, while `#!cpp json(true).get<std::int64_t>()` and
|
||||
`#!cpp json(true).get<double>()` throw. This is the behavior of all 3.x versions;
|
||||
[`basic_json_view::get<T>()`](../basic_json_view/get.md) behaves the same.
|
||||
|
||||
!!! note "`std::optional` conversions"
|
||||
|
||||
Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to
|
||||
|
||||
@@ -65,7 +65,12 @@ the same index, which is now a different value.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception occurs, the original value stays intact.
|
||||
Overload (1) gives the strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
Overloads (2) to (5) give the basic guarantee: if an exception is thrown while the elements or members are copied
|
||||
(for example, `std::bad_alloc`), the JSON value may be partially modified, but it stays a valid array or object.
|
||||
|
||||
The exceptions listed below are thrown before the JSON value is changed.
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
||||
@@ -63,6 +63,15 @@ called:
|
||||
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
|
||||
the latter case, it is skipped completely, or replaced by `null` if it is the top-level value.
|
||||
|
||||
## Notes
|
||||
|
||||
For a `parse_event_t::key` event, `parsed` holds the decoded object name, with the same guarantees as the `val`
|
||||
parameter of [`json_sax::key`](../json_sax/key.md#notes): all escape sequences are decoded and `\uXXXX` escapes are
|
||||
stored as UTF-8, so different spellings of the same name yield identical strings; the name is validated completely
|
||||
before the callback is called, so a name with an invalid escape sequence or ill-formed UTF-8 causes a parse error and
|
||||
no `key` event; no Unicode normalization or case conversion is applied; and a name that occurs several times in the
|
||||
same object yields one `key` event per occurrence.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: skip an object key while parsing"
|
||||
|
||||
@@ -15,7 +15,9 @@ the patch. This function applies a JSON patch in place and returns void.
|
||||
|
||||
## Exception safety
|
||||
|
||||
No guarantees, value may be corrupted by an unsuccessful patch operation.
|
||||
No guarantees: if an operation fails, the operations before it stay applied, and a failing `move` operation may already
|
||||
have removed the value at its "from" location. Use [`patch`](patch.md) if the value must be unchanged after a failed
|
||||
patch.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -62,13 +64,37 @@ the JSON value, the complexity can usually be neglected.
|
||||
## Notes
|
||||
|
||||
Unlike [`patch`](patch.md), `patch_inplace` applies the operation "in place" and no copy of the JSON value is created.
|
||||
That makes it faster for large documents by avoiding the copy. However, the JSON value might be corrupted if the
|
||||
function throws an exception.
|
||||
That makes it faster for large documents by avoiding the copy.
|
||||
|
||||
!!! warning "Not atomic"
|
||||
|
||||
`patch_inplace` applies the operations one after the other and does not undo them when one fails. After an
|
||||
exception, the value holds the result of the operations before the failing one, and a failing `move` operation
|
||||
(a "remove" followed by an "add") may already have removed the value at its "from" location; see the second example
|
||||
below. [RFC 6902](https://tools.ietf.org/html/rfc6902#section-5) requires that a patch with a failing operation is
|
||||
not deemed successful, but leaves open what happens to the document.
|
||||
|
||||
If the value must stay unchanged when the patch fails, use [`patch`](patch.md), which applies the patch to a copy:
|
||||
`#!cpp j = j.patch(json_patch);`. Validating the patch beforehand is not enough, as "test" operations and missing
|
||||
paths are only detected while the patch is applied.
|
||||
|
||||
Duplicate member names inside an operation object are resolved by the parser before the patch is applied (the last one
|
||||
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
|
||||
`remove` operation.
|
||||
|
||||
The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the operations are then read from a copy of
|
||||
the patch made before the first operation is applied, so the result is the same as that of
|
||||
`#!cpp j = j.patch(j)`, and the exception safety is the same as above.
|
||||
|
||||
If [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) is enabled, the same holds for a JSON patch stored inside the
|
||||
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the parent pointers.
|
||||
|
||||
!!! danger "Undefined behavior"
|
||||
|
||||
Without [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md), the JSON patch must not be a value stored inside the
|
||||
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`), as the operations may change or destroy it while it is
|
||||
read. Pass a copy (`#!cpp j.patch_inplace(json(j["patch"]))`) or use [`patch`](patch.md) instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: apply a JSON patch in place"
|
||||
@@ -122,3 +148,5 @@ is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op":
|
||||
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is
|
||||
not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
|
||||
- Applying a JSON value as patch to itself (or, with `JSON_DIAGNOSTICS`, to a value stored inside it) no longer results
|
||||
in undefined behavior in version 3.13.0.
|
||||
@@ -104,7 +104,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
|
||||
## Return value
|
||||
|
||||
return value of the last processed SAX event
|
||||
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
||||
(see [error recovery](../../features/parsing/error_recovery.md)).
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -163,6 +165,7 @@ use the same file with stdio functions, but it must not wait for another thread
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Added `tag_handler` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
|
||||
@@ -13,9 +13,15 @@ function never throws an exception for invalid input, and the returned `#!cpp bo
|
||||
is returned.
|
||||
|
||||
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
|
||||
function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX
|
||||
parser with a handler that ignores every event. Use this one if you go on to parse the same input with
|
||||
[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer.
|
||||
function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while
|
||||
`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to
|
||||
parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain
|
||||
yes/no answer.
|
||||
|
||||
When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index.
|
||||
With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only
|
||||
the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to
|
||||
keep the code size down.)
|
||||
|
||||
## Template parameters
|
||||
|
||||
|
||||
@@ -68,9 +68,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
||||
because a reference token is used on a primitive value.
|
||||
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ For the types below, the conversion works directly on the flat index — no `Bas
|
||||
|
||||
- `#!cpp bool`
|
||||
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md))
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md#notes), except for `number_integer_t`, `number_unsigned_t` and
|
||||
`number_float_t`, which throw `type_error.302` for a boolean, as `BasicJsonType::get<T>()` does)
|
||||
- `#!cpp std::nullptr_t`
|
||||
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string
|
||||
- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's
|
||||
@@ -80,14 +81,13 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
||||
or whatever a user-provided `from_json()` throws.
|
||||
|
||||
None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four
|
||||
bullets above) carry a
|
||||
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
||||
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
||||
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
||||
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
|
||||
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
|
||||
document.
|
||||
The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets
|
||||
above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the
|
||||
JSON Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception
|
||||
thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value
|
||||
instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree
|
||||
(with parent pointers), so it is empty if the failing value is the subtree's root itself.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
||||
`"type must be string, but is array"`.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -32,9 +32,24 @@ uses, so the result matches it exactly — including, for an object, that a repe
|
||||
last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion
|
||||
would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself.
|
||||
|
||||
Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values
|
||||
produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the
|
||||
end of arrays, objects, and escaped strings, which the positions also need.
|
||||
With [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values get the source
|
||||
positions [`BasicJsonType::parse()`](../basic_json/parse.md) would give them, also for the subtree of a view (the
|
||||
positions are those in the document's whole source text), and exceptions about them name the same bytes. For values of
|
||||
an [editable document](../json_editable_document.md), positions are all-or-nothing, and [`start_pos()`](../basic_json/start_pos.md)
|
||||
and [`end_pos()`](../basic_json/end_pos.md) return `std::string::npos` for
|
||||
|
||||
- a value written by an edit (a copy of an array or object of the document is also new; copies of its scalars keep
|
||||
their positions),
|
||||
- an array or object whose elements were changed (its elements keep theirs), and
|
||||
- an array or object whose last element has no positions (its end is found from that element).
|
||||
|
||||
This differs from a `basic_json` value, whose positions are not updated when it is changed: they stay as they were
|
||||
parsed, even if they no longer match the value (see [`start_pos()`](../basic_json/start_pos.md)).
|
||||
|
||||
Documents [loaded](../basic_json_document/load.md) from an image have the positions of the saved document. Its strings
|
||||
with escapes have positions only if the document was parsed with `JSON_DIAGNOSTIC_POSITIONS`: the position of their
|
||||
opening quote is recorded while parsing, and `save()` and `load()` keep it whether or not they are compiled with the
|
||||
macro.
|
||||
|
||||
Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it
|
||||
never caches the result.
|
||||
@@ -62,8 +77,7 @@ never caches the result.
|
||||
|
||||
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not
|
||||
produced by `materialize()`)
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
||||
`"type must be number, but is string"`.
|
||||
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -73,9 +73,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
||||
number.
|
||||
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -61,9 +61,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
||||
`out_of_range` and returns `default_value`.
|
||||
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ json_pointer& operator/=(std::size_t array_idx)
|
||||
## Parameters
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to append
|
||||
: JSON pointer to append; may be the JSON pointer itself (`#!cpp p /= p`), which then appends a copy of its
|
||||
reference tokens
|
||||
|
||||
`token` (in)
|
||||
: reference token to append
|
||||
@@ -69,6 +70,7 @@ json_pointer& operator/=(std::size_t array_idx)
|
||||
|
||||
## Version history
|
||||
|
||||
1. Added in version 3.6.0.
|
||||
1. Added in version 3.6.0. Appending a JSON pointer to itself no longer results in undefined behavior in version
|
||||
3.13.0.
|
||||
2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0.
|
||||
3. Added in version 3.6.0.
|
||||
@@ -7,7 +7,8 @@ struct json_sax;
|
||||
|
||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). 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.
|
||||
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||
[recover from the error](../../features/parsing/error_recovery.md).
|
||||
|
||||
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
||||
|
||||
|
||||
@@ -19,6 +19,23 @@ Whether parsing should proceed.
|
||||
|
||||
It is safe to move the passed object key value.
|
||||
|
||||
When parsing JSON text, `val` is the decoded object name, not its spelling in the input:
|
||||
|
||||
- All escape sequences (`\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`, and surrogate pairs) are
|
||||
decoded, and `\uXXXX` escapes are stored as UTF-8. Different spellings of the same name, such as `#!json "/"`
|
||||
and `#!json "\/"`, or `#!json "\u00e9"` and `#!json "\u00E9"`, yield identical strings. An escaped `\u0000` yields
|
||||
an embedded NUL byte.
|
||||
- The name is scanned and validated completely before `key` is called. If it contains an invalid escape sequence, an
|
||||
unpaired surrogate, a control character, or ill-formed UTF-8, the parse fails with a
|
||||
[`parse_error`](../../home/exceptions.md#parse-errors), and `key` is not called for it.
|
||||
- No Unicode normalization or case conversion is applied: names that differ in any byte are different strings.
|
||||
- `key` is called before the following `:` is read, so a parse error may still follow for the same member.
|
||||
- A name that occurs several times in the same object is passed once per occurrence.
|
||||
|
||||
For the binary formats, object keys contain no escape sequences. For CBOR, MessagePack, UBJSON, and BSON, `val` holds
|
||||
the bytes stored in the input, and [`sax_parse`](../basic_json/sax_parse.md) passes them unchanged, without checking them
|
||||
for valid UTF-8. BON8 encodes characters itself, so an ill-formed name is a parse error and `key` is not called for it.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -45,3 +62,7 @@ It is safe to move the passed object key value.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.2.0.
|
||||
- The behavior described in the notes above (decoded names, validation before `key` is called, no normalization, `key`
|
||||
before the `:` check, one call per duplicate, bytes of binary formats passed unchanged) is the same in all versions
|
||||
from 3.2.0 on, and for the `key` event of a [parser callback](../basic_json/parser_callback_t.md) from 3.0.0 on. BSON
|
||||
is only supported from version 3.4.0, and BON8 only from version 3.13.0.
|
||||
@@ -21,11 +21,18 @@ A parse error occurred.
|
||||
|
||||
## Return value
|
||||
|
||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
Whether to recover from the error:
|
||||
|
||||
- `#!cpp false` stops parsing.
|
||||
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
||||
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
||||
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
||||
|
||||
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
??? example "Example: (1) the SAX interface"
|
||||
|
||||
The example below shows how the SAX interface is used.
|
||||
|
||||
@@ -39,13 +46,30 @@ Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
--8<-- "examples/sax_parse.output"
|
||||
```
|
||||
|
||||
??? example "Example: (2) recovering from errors"
|
||||
|
||||
The example below shows how a SAX parser recovers from errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [`sax_parse`](../basic_json/sax_parse.md) - SAX parser
|
||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
||||
exceptions
|
||||
- [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors
|
||||
- [SAX Interface](../../features/parsing/sax_interface.md) - guide to event-based parsing
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.2.0.
|
||||
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
||||
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
||||
@@ -35,6 +35,9 @@ Note that enabling this macro increases the size of every JSON value by two `std
|
||||
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
||||
these values to be reported in those error messages.
|
||||
|
||||
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
|
||||
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (position diagnostics are switched off).
|
||||
@@ -113,6 +116,7 @@ When the macro is not defined, the library will define it to its default value.
|
||||
|
||||
- [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro
|
||||
- [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics
|
||||
- [`materialize()`](../basic_json_view/materialize.md) - values of a JSON view, with the same positions
|
||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
|
||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions. This also turns off the
|
||||
scan of the `FILE`'s buffer in bulk (see below), which [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)
|
||||
does not affect.
|
||||
|
||||
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
||||
Only the speed and the locking of the file differ (see the notes on locking below).
|
||||
@@ -37,9 +39,17 @@ The default value is `0`: the file is locked once and read with the unlocked std
|
||||
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
||||
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
||||
|
||||
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
|
||||
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
|
||||
With glibc and the Apple C library, the parser also scans the characters that the `FILE` already holds in its
|
||||
buffer in bulk, as it scans the get area of a stream (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)): it reads the same fields of the `FILE` that
|
||||
the C library's own `getc_unlocked` reads, and it consumes exactly the characters that `getc_unlocked` would have
|
||||
returned, so the file is left where it was left before.
|
||||
|
||||
Locking the file once makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3
|
||||
times as fast with glibc, whose locking is cheaper. Scanning the buffer in bulk makes `parse()` another 1.7 to 2.6
|
||||
times as fast (and `accept()` 2.9 to 4.4 times), about as fast as parsing from a `std::ifstream`. The binary
|
||||
formats ([`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md), ...) read
|
||||
from a `FILE*` the same way.
|
||||
|
||||
!!! note "Locking"
|
||||
|
||||
|
||||
@@ -13,80 +13,6 @@ describe each of them.
|
||||
|
||||
<p class="nj-caption" markdown>The checks grouped by the stage at which they run, from push or pull request through the merge into develop to a release. The badge on each card says whether the check runs on every push or PR, on a schedule, or continuously.</p>
|
||||
|
||||
## How CI runs
|
||||
|
||||
Not every job runs for every change. The compiler jobs on Ubuntu, macOS, and Windows are split into three sets, so a
|
||||
pull request gets fast feedback and the long jobs run when they matter:
|
||||
|
||||
| Set | When it runs | What it contains |
|
||||
|-------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| PR set | every push to a pull request, and every push to `develop`, `master`, and `release/*` | GCC and Clang with all warnings, Clang-Tidy, sanitizers, IWYU, cpplint, the oldest and newest GCC and Clang, common CMake options, Meson, Emscripten, four Visual Studio 2022 and two Visual Studio 2026 configurations, MinGW, Clang on Windows, ARM64 Windows, five Xcode versions, Swift Package Manager; with the checks that always run, they would have caught at least 92 % of the failing commits of September and October 2026 |
|
||||
| full set | after the PR set passed: on pull requests with the label :rocket: ready to merge, and on every push | every other compiler, version, C++ standard, and CMake option, the Clang Static Analyzer, Cppcheck, and the C++20 module tests |
|
||||
| nightly set | every night on `develop`, on every push to `master` and `release/*`, and when started by hand | Infer, Valgrind, coverage, the Intel and NVIDIA compilers, CUDA, ARM64 Linux, and 64-bit MinGW; a failure opens an issue |
|
||||
|
||||
The documentation and license checks and the amalgamation check run on every push and pull request; AppVeyor (Visual
|
||||
Studio 2015, 2017, and 2019) has its own schedule. A pull request that changes only the documentation, Markdown files, GitHub metadata, or license files
|
||||
skips the compiler workflows (see [`tools/check_path_filters`](https://github.com/nlohmann/json/tree/develop/tools/check_path_filters)).
|
||||
|
||||
A maintainer adds the label :rocket: ready to merge when a pull request is ready; the full set then runs, and again on
|
||||
every later push to that pull request. A pull request is merged only when the full set passed.
|
||||
|
||||
??? abstract "Jobs of the compiler workflows and their sets"
|
||||
|
||||
The table is generated by [`tools/ci_jobs`](https://github.com/nlohmann/json/tree/develop/tools/ci_jobs), which
|
||||
also checks in CI that it is up to date and that every job belongs to exactly one set.
|
||||
|
||||
<!-- ci_jobs: begin -->
|
||||
| Set | Workflow | Job | Configurations |
|
||||
|-----|----------|-----|----------------|
|
||||
| PR set | macos.yml | `macos-15` | 16.0; 16.4; 26.3 |
|
||||
| PR set | macos.yml | `macos-26` | 26.6 |
|
||||
| PR set | macos.yml | `xcode_standards` | 14 |
|
||||
| PR set | macos.yml | `swiftpm` | - |
|
||||
| PR set | ubuntu.yml | `ci_test_gcc` | - |
|
||||
| PR set | ubuntu.yml | `ci_meson_install` | - |
|
||||
| PR set | ubuntu.yml | `ci_static_analysis_ubuntu` | ci_cpplint |
|
||||
| PR set | ubuntu.yml | `ci_static_analysis_clang` | ci_test_clang; ci_clang_tidy; ci_test_clang_sanitizer; ci_single_binaries |
|
||||
| PR set | ubuntu.yml | `ci_cmake_options` | ci_test_diagnostics; ci_test_diagnostic_positions; ci_test_noexceptions; ci_test_noglobaludls |
|
||||
| PR set | ubuntu.yml | `ci_test_compilers_gcc_old` | 4.8 |
|
||||
| PR set | ubuntu.yml | `ci_test_compilers_clang` | 3.4 |
|
||||
| PR set | ubuntu.yml | `ci_test_standards_gcc` | 26 |
|
||||
| PR set | ubuntu.yml | `ci_test_standards_clang` | 26, libcxx |
|
||||
| PR set | ubuntu.yml | `ci_emscripten` | - |
|
||||
| PR set | windows.yml | `mingw` | x86 |
|
||||
| PR set | windows.yml | `msvc` | Debug, x64, default; Release, x64, default; Debug, Win32, default; Release, Win32, latest |
|
||||
| PR set | windows.yml | `msvc-vs2026` | Debug, x64, latest; Release, Win32, default |
|
||||
| PR set | windows.yml | `msvc-arm64` | Debug |
|
||||
| PR set | windows.yml | `clang` | 20.1.8 |
|
||||
| PR set | windows.yml | `ci_module_cpp20` | - |
|
||||
| full set | macos.yml | `macos-15_full` | 16.1; 16.2; 16.3; 26.0.1; 26.1.1; 26.2 |
|
||||
| full set | macos.yml | `macos-26_full` | 26.4.1; 26.5 |
|
||||
| full set | macos.yml | `xcode_standards_full` | 11; 17; 20; 23; 26 |
|
||||
| full set | ubuntu.yml | `ci_static_analysis_ubuntu_full` | ci_test_single_header; ci_cppcheck; ci_reproducible_tests; ci_non_git_tests; ci_offline_testdata |
|
||||
| full set | ubuntu.yml | `ci_static_analysis_clang_full` | ci_clang_analyze |
|
||||
| full set | ubuntu.yml | `ci_cmake_options_full` | ci_cmake_flags; ci_test_noimplicitconversions; ci_test_legacycomparison; ci_test_disableenumserialization; ci_test_disabletuplereferenceconversion; ci_test_skiplibraryversioncheck; ci_test_simdutf; ci_test_strict_nul_handling; ci_test_delete_deprecated_functions; ci_test_no_thread_local |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_gcc_old_full` | 4.9; 5; 6 |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_gcc_full` | 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; latest |
|
||||
| full set | ubuntu.yml | `ci_test_compilers_clang_full` | 3.5; 3.6; 3.7; 3.8; 3.9; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15-bullseye; 16; 17; 18; 19; 20; 21; 22; latest |
|
||||
| full set | ubuntu.yml | `ci_test_standards_gcc_full` | 11; 14; 17; 20; 23 |
|
||||
| full set | ubuntu.yml | `ci_test_standards_clang_full` | 11, libcxx; 11, libstdcxx; 14, libcxx; 14, libstdcxx; 17, libcxx; 17, libstdcxx; 20, libcxx; 20, libstdcxx; 23, libcxx; 23, libstdcxx; 26, libstdcxx |
|
||||
| full set | ubuntu.yml | `ci_module_cpp20` | gcc:latest; silkeh/clang:latest |
|
||||
| full set | windows.yml | `msvc_full` | Debug, Win32, latest; Debug, x64, latest; Release, Win32, default; Release, x64, latest |
|
||||
| full set | windows.yml | `msvc-vs2026_full` | Debug, Win32, default; Debug, Win32, latest; Debug, x64, default; Release, Win32, latest; Release, x64, default; Release, x64, latest |
|
||||
| full set | windows.yml | `msvc-arm64_full` | Release |
|
||||
| full set | windows.yml | `clang_full` | 11.0.1; 12.0.1; 13.0.1; 14.0.6; 15.0.7; 16.0.6; 18.1.8; 19.1.7 |
|
||||
| full set | windows.yml | `clang-cl_full` | Win32; x64 |
|
||||
| nightly set | ubuntu.yml | `ci_infer` | - |
|
||||
| nightly set | ubuntu.yml | `ci_static_analysis_ubuntu_nightly` | ci_test_valgrind |
|
||||
| nightly set | ubuntu.yml | `ci_test_coverage` | - |
|
||||
| nightly set | ubuntu.yml | `ci_cuda_example` | 11.8.0; 12.1.1; 12.6.3 |
|
||||
| nightly set | ubuntu.yml | `ci_icpc` | - |
|
||||
| nightly set | ubuntu.yml | `ci_icpx` | - |
|
||||
| nightly set | ubuntu.yml | `ci_nvhpc` | - |
|
||||
| nightly set | ubuntu.yml | `ci_test_arm64` | - |
|
||||
| nightly set | windows.yml | `mingw_nightly` | x64 |
|
||||
<!-- ci_jobs: end -->
|
||||
|
||||
## C++ language compliance and compiler compatibility
|
||||
|
||||
!!! success "Requirement: Compiler support"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// a SAX parser that creates a JSON value like json::parse does, but that
|
||||
// recovers from parse errors instead of stopping at the first one
|
||||
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
||||
{
|
||||
public:
|
||||
explicit recovering_parser(json& result)
|
||||
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
||||
{}
|
||||
|
||||
bool parse_error(std::size_t position,
|
||||
const std::string& /*last_token*/,
|
||||
const json::exception& ex)
|
||||
{
|
||||
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
||||
|
||||
// repair the input and continue
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// JSON text with several mistakes that ends too early
|
||||
const std::string text = R"({
|
||||
"name": "Hello World",
|
||||
"tags": ["a" "b",],
|
||||
"valid": tru,
|
||||
"size": 1.,
|
||||
"nested": {"x": 1)";
|
||||
|
||||
json result;
|
||||
recovering_parser sax(result);
|
||||
const bool valid = json::sax_parse(text, &sax);
|
||||
|
||||
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
||||
<< std::setw(4) << result << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
||||
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
||||
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
||||
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
||||
|
||||
valid JSON: false
|
||||
{
|
||||
"name": "Hello World",
|
||||
"nested": {
|
||||
"x": 1
|
||||
},
|
||||
"size": 1,
|
||||
"tags": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"valid": null
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# Reading and writing files
|
||||
|
||||
The library has no functions that take a file name. It reads from and writes to streams, so files are handled with
|
||||
`#!cpp std::ifstream` and `#!cpp std::ofstream` (or a `#!cpp FILE*` when reading). This page collects the recipes for
|
||||
the common cases and the mistakes that show up again and again.
|
||||
|
||||
## Reading a JSON file
|
||||
|
||||
Pass the stream to [`parse`](../api/basic_json/parse.md) directly, and check that the file was opened:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
json read_json_file(const std::string& path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error("cannot open " + path);
|
||||
}
|
||||
return json::parse(f);
|
||||
}
|
||||
```
|
||||
|
||||
- **Check that the file is open.** A stream that failed to open looks like an empty input, so `parse` reports
|
||||
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) ("unexpected end of input") at line 1,
|
||||
column 1 instead of saying that the file is missing. A relative path is resolved against the working directory of the
|
||||
running program, which is often not the directory of the source file (see the
|
||||
[FAQ](../home/faq.md#parsing-a-file-fails-with-unexpected-end-of-input)).
|
||||
- **Don't read the file into a string first.** Parsing from a `#!cpp std::ifstream` is only about 10 to 20 % slower
|
||||
than reading the whole file into a string with one `read()` and parsing that, and it needs no memory for the copy (see
|
||||
[input sources and performance](parsing/index.md#input-sources-and-performance)). The common way to read a file with
|
||||
`#!cpp std::istreambuf_iterator<char>` is so slow that it takes about twice as long as parsing from the stream. Read
|
||||
the file into a string only if you need the text itself, for example for an error message. This advice is for
|
||||
`#!cpp json::parse`. [`json_document`](json_view.md) reads a stream into a buffer that it owns anyway, so there is no
|
||||
copy to save: reading the file into a `#!cpp std::string` yourself and passing that as an lvalue, which the document
|
||||
borrows, was about 6 to 12 % faster than parsing from the stream, with the same single buffer.
|
||||
- `parse` requires the file to hold exactly one JSON value. Files with one value per line are read with
|
||||
[JSON Lines](parsing/json_lines.md); files too large for memory are covered in
|
||||
[Large, streamed, and partial input](parsing/large_and_partial_input.md).
|
||||
|
||||
## Writing a JSON file
|
||||
|
||||
[`dump`](../api/basic_json/dump.md) the value into a string first, then write the string:
|
||||
|
||||
```cpp
|
||||
void write_json_file(const std::string& path, const json& j)
|
||||
{
|
||||
const std::string text = j.dump(4) + '\n'; // may throw: nothing is written yet
|
||||
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::runtime_error("cannot open " + path);
|
||||
}
|
||||
f << text;
|
||||
f.close();
|
||||
if (f.fail())
|
||||
{
|
||||
throw std::runtime_error("cannot write " + path);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Serialize before opening the file.** Opening a `#!cpp std::ofstream` truncates the file. `dump` and
|
||||
[`operator<<`](../api/operator_ltlt.md) throw [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316)
|
||||
when a string is not valid UTF-8 (see [handling invalid UTF-8](serialization.md#handling-invalid-utf-8)), and
|
||||
`#!cpp f << std::setw(4) << j` may already have written part of the value when it throws. Serializing into a string
|
||||
first leaves the old file untouched in that case.
|
||||
- **Check the stream after closing it.** A full disk or a missing permission sets the stream's error state; it does not
|
||||
throw unless you enabled exceptions on the stream with `exceptions()`.
|
||||
- `std::ios::binary` writes the text as it is. Without it, Windows writes `\r\n` for every newline of a pretty-printed
|
||||
value.
|
||||
|
||||
## Replacing a file without corrupting it
|
||||
|
||||
The library never writes a file itself, so this section is not about the library. It is general C++ advice for a
|
||||
problem users of the library keep running into.
|
||||
|
||||
A program that crashes or is killed while it writes leaves a truncated file behind. To make sure readers see either the
|
||||
old or the new contents, write to a temporary file in the same directory and rename it over the original
|
||||
(C++17, `#!cpp <filesystem>`):
|
||||
|
||||
```cpp
|
||||
#include <filesystem>
|
||||
|
||||
void replace_json_file(const std::filesystem::path& path, const json& j)
|
||||
{
|
||||
std::filesystem::path tmp = path;
|
||||
tmp += ".tmp"; // use a unique name if several processes may write the file
|
||||
|
||||
write_json_file(tmp.string(), j);
|
||||
std::filesystem::rename(tmp, path); // replaces path
|
||||
}
|
||||
```
|
||||
|
||||
The temporary file must be in the same directory (more precisely, on the same file system) as the target: a rename
|
||||
across file systems does not fall back to a copy, it fails and throws `#!cpp std::filesystem::filesystem_error`. On
|
||||
POSIX systems, the rename atomically replaces the file. Standard C++ has no way to flush a file to the disk (`fsync`),
|
||||
so this protects against a crashed program, but not necessarily against a power loss.
|
||||
|
||||
## Binary formats
|
||||
|
||||
The [binary formats](binary_formats/index.md) work the same way, with the streams opened in binary mode:
|
||||
|
||||
```cpp
|
||||
// read
|
||||
std::ifstream in("data.cbor", std::ios::binary);
|
||||
json j = json::from_cbor(in);
|
||||
|
||||
// write
|
||||
const std::vector<std::uint8_t> bytes = json::to_cbor(j);
|
||||
std::ofstream out("data.cbor", std::ios::binary);
|
||||
out.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
||||
```
|
||||
|
||||
Without `std::ios::binary`, Windows turns the bytes `0x0D 0x0A` into `0x0A` and may stop at a byte `0x1A` while reading,
|
||||
and turns `0x0A` into `0x0D 0x0A` while writing, which corrupts the data.
|
||||
|
||||
## See also
|
||||
|
||||
- [Parsing](parsing/index.md) - the inputs `parse` accepts
|
||||
- [Serialization](serialization.md) - the options of `dump`
|
||||
- [`parse`](../api/basic_json/parse.md), [`dump`](../api/basic_json/dump.md)
|
||||
- [`operator>>`](../api/operator_gtgt.md), [`operator<<`](../api/operator_ltlt.md)
|
||||
@@ -46,6 +46,8 @@ C++ types, and finally serialize it again.
|
||||
[BJData](binary_formats/bjdata.md), [BON8](binary_formats/bon8.md), [BSON](binary_formats/bson.md),
|
||||
[CBOR](binary_formats/cbor.md), [MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
|
||||
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
|
||||
- [Reading and writing files](files.md) — read and write files safely, including replacing a file without
|
||||
corrupting it.
|
||||
|
||||
## How values are stored and configured
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ other three continue on its result.
|
||||
|
||||
[`patch`](../api/basic_json/patch.md) leaves the original value unchanged and returns the patched result as a copy.
|
||||
If the document is large and the original value is no longer needed,
|
||||
[`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead.
|
||||
[`patch_inplace`](../api/basic_json/patch_inplace.md) applies the same operations in place instead. Unlike
|
||||
`patch`, it is not atomic: if an operation fails, the operations before it stay applied.
|
||||
|
||||
!!! note "Duplicate member names"
|
||||
|
||||
|
||||
@@ -134,9 +134,21 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
||||
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
||||
how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md)
|
||||
needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through
|
||||
`BasicJsonType`.
|
||||
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
|
||||
`#!cpp basic_json`:
|
||||
|
||||
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
|
||||
value within the document.
|
||||
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
|
||||
the value in the source text.
|
||||
|
||||
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
|
||||
exception is thrown. This takes time linear in the size of the document. One exception has neither:
|
||||
`key()` of an iterator over an array or a primitive value
|
||||
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
|
||||
not know the value it iterates over.
|
||||
|
||||
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
|
||||
|
||||
## What is different
|
||||
|
||||
@@ -148,11 +160,15 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
|
||||
end must still fit.
|
||||
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||
- **No source positions on `materialize()`.** Even with
|
||||
is read to its end into a buffer the document owns, and parsing starts after that. For a file, reading it into a
|
||||
`#!cpp std::string` yourself and parsing that (an lvalue is borrowed) is therefore not more expensive in memory, and
|
||||
was about 6 to 12 % faster than parsing from an `#!cpp std::ifstream`.
|
||||
- **Fewer source positions after edits.** With
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every
|
||||
value, but not the end of arrays, objects, and escaped strings, which the positions also need.
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
|
||||
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
|
||||
an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions
|
||||
when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes).
|
||||
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
|
||||
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
|
||||
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
|
||||
@@ -165,10 +181,6 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
||||
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
||||
below.
|
||||
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
|
||||
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||
`BasicJsonType` was built.
|
||||
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
||||
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
||||
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
||||
@@ -251,6 +263,34 @@ Two conversions never copy at all:
|
||||
|
||||
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
|
||||
|
||||
### `string_view` members
|
||||
|
||||
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
|
||||
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
|
||||
into the document's source text, and for a string with escape sequences into the document's buffer of decoded strings.
|
||||
The member is valid as long as the document is alive and, for a document that
|
||||
[borrows its source text](#ownership-and-lifetime) (as [`parse()`](../api/basic_json_document/parse.md) does for
|
||||
an lvalue `#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
|
||||
|
||||
```cpp
|
||||
struct record
|
||||
{
|
||||
std::string_view name;
|
||||
int count;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
|
||||
};
|
||||
|
||||
const std::string text = R"({"name": "widget", "count": 3})";
|
||||
const json_document doc = json_document::parse(text); // borrows text
|
||||
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
|
||||
```
|
||||
|
||||
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
|
||||
|
||||
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
|
||||
temporary `json` value, which is destroyed at the end of the statement. Use a named
|
||||
[`json_document`](../api/json_document.md), as above, to decode such a struct.
|
||||
|
||||
## Writing generic code
|
||||
|
||||
A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md):
|
||||
@@ -327,14 +367,10 @@ Its body may only use what views and `basic_json` have in common: the type check
|
||||
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
||||
converted through `materialize()` as well.
|
||||
|
||||
Two things differ when a `from_json()` reads the view:
|
||||
|
||||
- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member
|
||||
with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get<T>()` would see the last one.
|
||||
Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as
|
||||
[`parse()`](../api/basic_json/parse.md) does.
|
||||
- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the
|
||||
value.
|
||||
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
|
||||
the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys);
|
||||
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
|
||||
keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does.
|
||||
|
||||
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# Error Recovery
|
||||
|
||||
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
||||
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
||||
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
||||
|
||||
## Recovering from errors
|
||||
|
||||
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
||||
decides what happens next:
|
||||
|
||||
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
||||
and [`accept`](../../api/basic_json/accept.md) never recover.
|
||||
- `#!cpp true` repairs the error and continues parsing.
|
||||
|
||||
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
||||
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
||||
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
||||
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
||||
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
||||
`#!cpp false`.
|
||||
|
||||
!!! example
|
||||
|
||||
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
||||
and recovers from all errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## How errors are repaired
|
||||
|
||||
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
||||
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
||||
|
||||
| Mistake | Repair | Example | Result |
|
||||
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
||||
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
||||
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
||||
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
||||
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
||||
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
||||
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
||||
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
||||
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
||||
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
||||
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
||||
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
||||
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
||||
|
||||
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
||||
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
||||
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
||||
|
||||
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
||||
|
||||
## Binary formats
|
||||
|
||||
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
||||
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
||||
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
||||
on whether the end of the item with the error is known, a distinction that
|
||||
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
||||
|
||||
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
||||
|
||||
| Mistake | Formats | Repair |
|
||||
|---------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------|
|
||||
| tag, if `tag_handler` is `cbor_tag_handler_t::error` (the default) | CBOR | ignored |
|
||||
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
||||
| number too large for a custom `number_float_t`, like `float` | all | infinity |
|
||||
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
||||
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
||||
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
||||
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
||||
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
||||
| string without its terminator | BSON | kept |
|
||||
| document whose size does not match its content | BSON | kept |
|
||||
|
||||
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
||||
suggests for converting CBOR to JSON. By default, [`sax_parse`](../../api/basic_json/sax_parse.md) reports every tag as
|
||||
an error; when recovering, tags are then ignored like with
|
||||
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md). Strings that are not valid UTF-8 are no
|
||||
error: like [`from_cbor`](../../api/basic_json/from_cbor.md) and the other functions by default, `sax_parse` passes
|
||||
them on as they are.
|
||||
|
||||
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
||||
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
||||
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
||||
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
||||
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
||||
|
||||
## Limitations
|
||||
|
||||
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
||||
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
||||
reported errors.
|
||||
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
||||
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
||||
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
||||
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
||||
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
||||
BSON document after one whose end is unknown.
|
||||
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
||||
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
||||
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
||||
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
||||
input after the value, for instance of the next value in a stream of concatenated values.
|
||||
|
||||
## See also
|
||||
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
@@ -35,6 +35,8 @@ std::ifstream f("example.json");
|
||||
json data = json::parse(f);
|
||||
```
|
||||
|
||||
See [Reading and writing files](../files.md) for checking that the file was opened and for writing files safely.
|
||||
|
||||
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
|
||||
is read as UTF-16 or UTF-32 — depending on the character type — and converted to UTF-8; see the
|
||||
[`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value.
|
||||
@@ -75,7 +77,7 @@ them faster than others:
|
||||
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
||||
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
||||
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
|
||||
| `#!cpp FILE*` | with glibc and the Apple C library, the characters the `FILE` holds in its buffer are scanned in bulk like a stream's; elsewhere one character at a time; the file is locked once for the whole parse |
|
||||
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
||||
|
||||
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
||||
@@ -84,6 +86,9 @@ ahead: after parsing, it is positioned exactly as if every character had been re
|
||||
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
|
||||
[JSON Lines](json_lines.md)).
|
||||
|
||||
This does not hold for [`json_document`](../json_view.md): it reads a stream into a buffer it owns, so the stream saves
|
||||
no memory, and reading the file into a `#!cpp std::string` yourself and borrowing it is about 6 to 12 % faster.
|
||||
|
||||
Two macros switch these optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
|
||||
follow the usual `std::streambuf` contract, and
|
||||
@@ -141,7 +146,7 @@ See [Zero-copy JSON views](../json_view.md) for the concept and the trade-offs.
|
||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||
options.
|
||||
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||
|
||||
## See also
|
||||
|
||||
@@ -155,6 +160,7 @@ options.
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [Zero-copy JSON views](../json_view.md) - parse into a read-only document and read it through views
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
|
||||
- [`json_sax`](../../api/json_sax/index.md) - documentation of the SAX interface
|
||||
|
||||
@@ -68,7 +68,8 @@ bool parse_error(std::size_t position,
|
||||
const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
||||
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||
|
||||
??? example "Example: report parse errors without exceptions"
|
||||
|
||||
|
||||
@@ -62,7 +62,11 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||
|
||||
Object keys are passed to `key` decoded and validated: escape sequences are already resolved, so different spellings of
|
||||
the same name yield the same string. See [`key`](../../api/json_sax/key.md#notes) for the exact guarantees.
|
||||
|
||||
## Stepping through the events
|
||||
|
||||
@@ -98,7 +102,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scan
|
||||
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
||||
locks the file once per parse instead of once per character. See
|
||||
area is read one character at a time. A `#!cpp FILE*` is locked once per parse instead of once per character, and with
|
||||
glibc and the Apple C library, the characters in its buffer are scanned in bulk as a stream's get area is. See
|
||||
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
||||
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
||||
parsing an equivalent `#!cpp std::string`.
|
||||
@@ -39,7 +39,9 @@ iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::de
|
||||
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
|
||||
|
||||
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
|
||||
into a string first is no longer necessary for speed:
|
||||
into a string first is no longer necessary for speed (this is for `#!cpp json::parse`; a
|
||||
[`json_document`](json_view.md) reads a stream into an owned buffer anyway, so for it reading the file into a string
|
||||
and borrowing that is the faster way):
|
||||
|
||||
```cpp
|
||||
// fast: the stream buffer's get area is scanned in bulk
|
||||
|
||||
@@ -17,6 +17,9 @@ To write a value directly to a stream (for example, a file or `#!cpp std::cout`)
|
||||
std::cout << j << std::endl;
|
||||
```
|
||||
|
||||
To write a file without leaving a truncated file behind when serialization fails, see
|
||||
[Reading and writing files](files.md#writing-a-json-file).
|
||||
|
||||
!!! note "String, not raw value"
|
||||
|
||||
`dump` always returns a **JSON text**. Serializing a JSON string therefore includes the surrounding quotes and
|
||||
|
||||
@@ -406,6 +406,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
||||
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
||||
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
||||
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
|
||||
|
||||
### Compatible types
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ packet-beta
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself; strings with escapes: the position of the opening quote in the source plus one if the document was parsed with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), or 0 if it was not; all other values: 0 |
|
||||
|
||||
<p class="nj-table-caption" markdown>Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.</p>
|
||||
|
||||
@@ -218,6 +218,12 @@ packet-beta
|
||||
step from element to element this way and skip whole subtrees in constant time.
|
||||
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
|
||||
2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
|
||||
- **Source positions** ([`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) are derived by
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) and cost almost nothing while parsing: the only work is
|
||||
storing the offset of the opening quote of a string with escapes in its `next`, which is otherwise unused. A
|
||||
scalar's range follows from `off` and its length, a string with escapes is scanned from its opening quote (`next`) to the closing one,
|
||||
and the end of an array or object is found by scanning from the end of its last element over whitespace, comments,
|
||||
and a trailing comma. `materialize()` hands these positions to the SAX builder of `parse()` in place of its lexer.
|
||||
- **Large objects** (128 members or more) get a hash index for their keys after parsing
|
||||
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||
@@ -277,7 +283,7 @@ Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_j
|
||||
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
||||
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
||||
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
||||
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
rebuilds the indexes. The flag *new* is kept, as these values have no position in the text. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||
|
||||
## Input adapters
|
||||
|
||||
@@ -10,6 +10,13 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||
|
||||
The file also has visualizers for `json_document` and `json_view` (and their `ordered_json`/editable variants). They
|
||||
show the value of the document or view, and its elements as an expandable list. Because a document is only a flat index
|
||||
into the source text, the strings of the direct elements are shown, but the elements of nested arrays and objects are
|
||||
shown as index nodes (their kind, size, and numbers; strings only by length). Arrays and objects that have been edited
|
||||
in a `json_editable_document` are not expanded. The visualizers read the internal layout of the MSVC STL (`std::array`,
|
||||
`std::unique_ptr`).
|
||||
|
||||
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||
|
||||
@@ -29,7 +36,7 @@ ABI tags are not covered and may show raw internal fields instead. To generate a
|
||||
|
||||
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||
|
||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 11
|
||||
ABI tags, doubling with each further tag). See
|
||||
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ in the source files that need it, instantiate `basic_json` once, and consider pr
|
||||
The parser did not see any character: the file was most likely not opened. A relative path is resolved against the
|
||||
working directory of the running program, which is often the build directory of your IDE, not the source directory.
|
||||
Check `is_open()` and try an absolute path. Other causes are an empty file and a stream that was already read. See
|
||||
[`parse_error.101`](exceptions.md#jsonexceptionparse_error101).
|
||||
[`parse_error.101`](exceptions.md#jsonexceptionparse_error101) and [Reading and writing files](../features/files.md).
|
||||
|
||||
### Input that arrives in chunks
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ The class contains a port of the shortest double-to-decimal conversion of [Żmij
|
||||
|
||||
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
|
||||
The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||
|
||||
The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
The class's lexer and the view's parser (`<nlohmann/json_view.hpp>`) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
@@ -1,21 +1,21 @@
|
||||
<svg class="nj-fig nj-charts nj-charts--compile" viewBox="0 0 470 516" role="img" aria-labelledby="nj-charts-ct nj-charts-ct-d" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-charts nj-charts--compile" viewBox="0 0 470 620" role="img" aria-labelledby="nj-charts-ct nj-charts-ct-d" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-charts-ct">Change in compile time against the default configuration at -O0, Apple clang and G​CC 16</title>
|
||||
<desc id="nj-charts-ct-d">Grouped horizontal bars, negative is faster. json_fwd.hpp instead of json.hpp on a header-only translation unit: Apple clang -62 %, G​CC 16 -82 %; extern template on a parsing translation unit: Apple clang -7 %, G​CC 16 -21 %; JSON_NO_AUTOMATIC_UDLS on a model translation unit: Apple clang -11 %, G​CC 16 -11 %; JSON_NO_AUTOMATIC_UDLS on a parsing translation unit: Apple clang -1 %, G​CC 16 -2 %; JSON_HAS_FILESYSTEM=0 + JSON_NO_IO on a model translation unit: Apple clang -8 %, G​CC 16 -11 %; JSON_HAS_FILESYSTEM=0 on a model translation unit: Apple clang -5 %, G​CC 16 -9 %; JSON_NO_IO on a model translation unit: Apple clang -2 %, G​CC 16 -2 %; JSON_USE_GLOBAL_UDLS=0 on a model translation unit: Apple clang 0 %, G​CC 16 -1 %.</desc>
|
||||
<desc id="nj-charts-ct-d">Grouped horizontal bars, negative is faster. json_fwd.hpp instead of json.hpp on a header-only translation unit: Apple clang -62 %, G​CC 16 -82 %; extern template on a parsing translation unit: Apple clang -7 %, G​CC 16 -21 %; JSON_NO_AUTOMATIC_UDLS on a model translation unit: Apple clang -11 %, G​CC 16 -11 %; JSON_NO_AUTOMATIC_UDLS on a parsing translation unit: Apple clang -1 %, G​CC 16 -2 %; JSON_HAS_FILESYSTEM=0 + JSON_NO_IO on a model translation unit: Apple clang -8 %, G​CC 16 -11 %; JSON_HAS_FILESYSTEM=0 on a model translation unit: Apple clang -5 %, G​CC 16 -9 %; JSON_HAS_STD_FORMAT=0 on a model translation unit: Apple clang -1 %, G​CC 16 -4 %; JSON_HAS_RANGES=0 on a model translation unit: Apple clang -4 %, G​CC 16 -1 %; JSON_NO_IO on a model translation unit: Apple clang -2 %, G​CC 16 -2 %; JSON_USE_GLOBAL_UDLS=0 on a model translation unit: Apple clang 0 %, G​CC 16 -1 %.</desc>
|
||||
<rect class="nj-charts-clang" x="8" y="10" width="12" height="12" rx="3"/>
|
||||
<text class="nj-charts-leg" x="26" y="20.5">Apple clang</text>
|
||||
<rect class="nj-charts-gcc" x="126" y="10" width="12" height="12" rx="3"/>
|
||||
<text class="nj-charts-leg" x="144" y="20.5">G​CC 16</text>
|
||||
<line class="nj-charts-grid" x1="47.4" y1="58" x2="47.4" y2="472"/>
|
||||
<text class="nj-charts-tick" x="47.4" y="488">−80 %</text>
|
||||
<line class="nj-charts-grid" x1="126.2" y1="58" x2="126.2" y2="472"/>
|
||||
<text class="nj-charts-tick" x="126.2" y="488">−60 %</text>
|
||||
<line class="nj-charts-grid" x1="205" y1="58" x2="205" y2="472"/>
|
||||
<text class="nj-charts-tick" x="205" y="488">−40 %</text>
|
||||
<line class="nj-charts-grid" x1="283.8" y1="58" x2="283.8" y2="472"/>
|
||||
<text class="nj-charts-tick" x="283.8" y="488">−20 %</text>
|
||||
<text class="nj-charts-tick" x="362.6" y="488">0 %</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--l" x="8" y="506">← faster</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--r" x="462" y="506">slower →</text>
|
||||
<line class="nj-charts-grid" x1="47.4" y1="58" x2="47.4" y2="576"/>
|
||||
<text class="nj-charts-tick" x="47.4" y="592">−80 %</text>
|
||||
<line class="nj-charts-grid" x1="126.2" y1="58" x2="126.2" y2="576"/>
|
||||
<text class="nj-charts-tick" x="126.2" y="592">−60 %</text>
|
||||
<line class="nj-charts-grid" x1="205" y1="58" x2="205" y2="576"/>
|
||||
<text class="nj-charts-tick" x="205" y="592">−40 %</text>
|
||||
<line class="nj-charts-grid" x1="283.8" y1="58" x2="283.8" y2="576"/>
|
||||
<text class="nj-charts-tick" x="283.8" y="592">−20 %</text>
|
||||
<text class="nj-charts-tick" x="362.6" y="592">0 %</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--l" x="8" y="610">← faster</text>
|
||||
<text class="nj-charts-tick nj-charts-tick--r" x="462" y="610">slower →</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="62"><tspan class="nj-charts-code">json_fwd.hpp instead of json.hpp</tspan><tspan class="nj-charts-sub"> · header-only TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 70 H122.32 Q118.32 70 118.32 74 V78 Q118.32 82 122.32 82 H362.6 Z"><title>json_fwd.hpp instead of json.hpp, header-only TU (Apple clang): −62 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="80.5">−62 %</text>
|
||||
@@ -46,15 +46,25 @@
|
||||
<text class="nj-charts-val" x="367.6" y="340.5">−5 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 344 H331.14 Q327.14 344 327.14 348 V352 Q327.14 356 331.14 356 H362.6 Z"><title>JSON_HAS_FILESYSTEM=0, model TU (G​CC 16): −9 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="354.5">−9 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="374"><tspan class="nj-charts-code">JSON_NO_IO</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 382 H358.72 Q354.72 382 354.72 386 V390 Q354.72 394 358.72 394 H362.6 Z"><title>JSON_NO_IO, model TU (Apple clang): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="392.5">−2 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 396 H358.72 Q354.72 396 354.72 400 V404 Q354.72 408 358.72 408 H362.6 Z"><title>JSON_NO_IO, model TU (G​CC 16): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="406.5">−2 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="426"><tspan class="nj-charts-code">JSON_USE_GLOBAL_UDLS=0</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 434 h0.5 v12 h-0.5 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (Apple clang): 0 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="444.5">0 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 448 H362.6 Q358.66 448 358.66 451.94 V456.06 Q358.66 460 362.6 460 H362.6 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-rowlabel" x="8" y="374"><tspan class="nj-charts-code">JSON_HAS_STD_FORMAT=0</tspan><tspan class="nj-charts-sub"> · model TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 382 H362.6 Q358.66 382 358.66 385.94 V390.06 Q358.66 394 362.6 394 H362.6 Z"><title>JSON_HAS_STD_FORMAT=0, model TU (Apple clang): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="392.5">−1 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 396 H350.84 Q346.84 396 346.84 400 V404 Q346.84 408 350.84 408 H362.6 Z"><title>JSON_HAS_STD_FORMAT=0, model TU (G​CC 16): −4 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="406.5">−4 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="426"><tspan class="nj-charts-code">JSON_HAS_RANGES=0</tspan><tspan class="nj-charts-sub"> · model TU</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 434 H350.84 Q346.84 434 346.84 438 V442 Q346.84 446 350.84 446 H362.6 Z"><title>JSON_HAS_RANGES=0, model TU (Apple clang): −4 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="444.5">−4 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 448 H362.6 Q358.66 448 358.66 451.94 V456.06 Q358.66 460 362.6 460 H362.6 Z"><title>JSON_HAS_RANGES=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="458.5">−1 %</text>
|
||||
<line class="nj-charts-zero" x1="362.6" y1="58" x2="362.6" y2="472"/>
|
||||
<text class="nj-charts-rowlabel" x="8" y="478"><tspan class="nj-charts-code">JSON_NO_IO</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 486 H358.72 Q354.72 486 354.72 490 V494 Q354.72 498 358.72 498 H362.6 Z"><title>JSON_NO_IO, model TU (Apple clang): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="496.5">−2 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 500 H358.72 Q354.72 500 354.72 504 V508 Q354.72 512 358.72 512 H362.6 Z"><title>JSON_NO_IO, model TU (G​CC 16): −2 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="510.5">−2 %</text>
|
||||
<text class="nj-charts-rowlabel" x="8" y="530"><tspan class="nj-charts-code">JSON_USE_GLOBAL_UDLS=0</tspan><tspan class="nj-charts-sub"> · model TU*</tspan></text>
|
||||
<path class="nj-charts-clang" d="M362.6 538 h0.5 v12 h-0.5 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (Apple clang): 0 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="548.5">0 %</text>
|
||||
<path class="nj-charts-gcc" d="M362.6 552 H362.6 Q358.66 552 358.66 555.94 V560.06 Q358.66 564 362.6 564 H362.6 Z"><title>JSON_USE_GLOBAL_UDLS=0, model TU (G​CC 16): −1 %</title></path>
|
||||
<text class="nj-charts-val" x="367.6" y="562.5">−1 %</text>
|
||||
<line class="nj-charts-zero" x1="362.6" y1="58" x2="362.6" y2="576"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -1,31 +1,26 @@
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--wide" viewBox="0 0 760 862" role="img" aria-labelledby="nj-qa-title-w nj-qa-desc-w" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--wide" viewBox="0 0 760 808" role="img" aria-labelledby="nj-qa-title-w nj-qa-desc-w" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-qa-title-w">Quality assurance pipeline</title>
|
||||
<desc id="nj-qa-desc-w">A push or pull request starts these checks; the badges say when each runs: Build matrix (every push / PR, before merge): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings a subset on every push and PR, all before merge; Intel, NVIDIA, C​UDA, and ARM64 nightly; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (nightly): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Infer nightly; Dynamic analysis (every push / PR, nightly): runtime assertions Valgrind (Memcheck), nightly address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<desc id="nj-qa-desc-w">Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<defs><marker id="nj-qa-arrow-w" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="736" height="648" rx="12"/>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="736" height="594" rx="12"/>
|
||||
<rect class="nj-exceptions-stage" x="265" y="8" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="30">push or pull request</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="42" x2="380" y2="68" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel on GitHub Actions and AppVeyor</text>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="228" height="238" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="228" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="135">Build matrix</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="160">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="156.5" y="146" width="91.6" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="202.3" y="160">before merge</text>
|
||||
<text class="nj-exceptions-line" x="40" y="188">50+ compilers: G​CC, Clang, M​SVC,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="206">AppleClang, Intel, NVIDIA, ...</text>
|
||||
<text class="nj-exceptions-line" x="40" y="224">C++11, 14, 17, 20, 23, and 26</text>
|
||||
<text class="nj-exceptions-line" x="40" y="242">libc++ and libstdc++</text>
|
||||
<text class="nj-exceptions-line" x="40" y="260">warnings: Clang -Weverything,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="278">G​CC 300+ warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="296">a subset on every push and PR,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="314">all before merge; Intel, NVIDIA,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="332">C​UDA, and ARM64 nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="110" width="228" height="238" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="266" y="110" width="228" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="135">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="160">every push / PR</text>
|
||||
@@ -37,218 +32,209 @@
|
||||
<text class="nj-exceptions-line" x="280" y="278">and object types</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="110" width="228" height="238" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="506" y="110" width="228" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="135">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="520" y="146" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="550.05" y="160">nightly</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="160">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="520" y="188">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="520" y="206">of the code base</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="385">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="410">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="396" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="410">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="492">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="510">(also scheduled); Infer nightly</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="331">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="356">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="342" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="356">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="384">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="402">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="420">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">(also scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="385">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="410">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="396" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="426.55" y="410">nightly</text>
|
||||
<text class="nj-exceptions-line" x="280" y="438">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="280" y="456">Valgrind (Memcheck), nightly</text>
|
||||
<text class="nj-exceptions-line" x="280" y="474">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="280" y="492">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="280" y="510">nullability violations</text>
|
||||
<rect class="nj-exceptions-box" x="266" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="331">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="356">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="280" y="384">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="280" y="402">Valgrind (Memcheck)</text>
|
||||
<text class="nj-exceptions-line" x="280" y="420">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="280" y="438">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="280" y="456">nullability violations</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="360" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="385">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="396" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="553.2" y="410">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="592.4" y="396" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="631.9" y="410">continuous</text>
|
||||
<text class="nj-exceptions-line" x="520" y="438">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="520" y="456">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="520" y="474">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="520" y="492">reproducing unit test</text>
|
||||
<rect class="nj-exceptions-box" x="506" y="306" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="331">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="342" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="553.2" y="356">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="592.4" y="342" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="631.9" y="356">continuous</text>
|
||||
<text class="nj-exceptions-line" x="520" y="384">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="520" y="402">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="520" y="420">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="520" y="438">reproducing unit test</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="563">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="588">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="616">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="634">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="652">cpplint with 61 rules</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="509">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="534">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="562">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="580">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="598">cpplint with 61 rules</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="266" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="563">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="588">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="574" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="432.85" y="588">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="280" y="616">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="280" y="634">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="280" y="652">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="280" y="670">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="280" y="688">regularly (scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="266" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="280" y="509">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="280" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="335.25" y="534">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="396.5" y="520" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="432.85" y="534">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="280" y="562">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="280" y="580">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="280" y="598">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="280" y="616">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="280" y="634">regularly (scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="506" y="538" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="563">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="574" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="588">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="636.5" y="574" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="672.85" y="588">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="520" y="616">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="520" y="634">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="520" y="652">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="520" y="670">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="520" y="688">Scorecard, R​EUSE</text>
|
||||
<rect class="nj-exceptions-box" x="506" y="484" width="228" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="520" y="509">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="520" y="520" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="575.25" y="534">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="636.5" y="520" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="672.85" y="534">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="520" y="562">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="520" y="580">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="520" y="598">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="520" y="616">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="520" y="634">Scorecard, R​EUSE</text>
|
||||
</g>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="718" x2="380" y2="744" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="265" y="746" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="768">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="780" x2="380" y2="806" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="230" y="808" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="830">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="380" y="846">no open vulnerability alert</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="664" x2="380" y2="690" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="265" y="692" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="714">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="380" y1="726" x2="380" y2="752" marker-end="url(#nj-qa-arrow-w)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="230" y="754" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="380" y="776">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="380" y="792">no open vulnerability alert</text>
|
||||
</svg>
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--narrow" viewBox="0 0 360 1840" role="img" aria-labelledby="nj-qa-title-n nj-qa-desc-n" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="nj-fig nj-exceptions-qa nj-exceptions-qa--narrow" viewBox="0 0 360 1786" role="img" aria-labelledby="nj-qa-title-n nj-qa-desc-n" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="nj-qa-title-n">Quality assurance pipeline</title>
|
||||
<desc id="nj-qa-desc-n">A push or pull request starts these checks; the badges say when each runs: Build matrix (every push / PR, before merge): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings a subset on every push and PR, all before merge; Intel, NVIDIA, C​UDA, and ARM64 nightly; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (nightly): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Infer nightly; Dynamic analysis (every push / PR, nightly): runtime assertions Valgrind (Memcheck), nightly address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<desc id="nj-qa-desc-n">Every push or pull request starts these checks in parallel: Build matrix (every push / PR): 50+ compilers: G​CC, Clang, M​SVC, AppleClang, Intel, NVIDIA, ... C++11, 14, 17, 20, 23, and 26 libc++ and libstdc++ warnings: Clang -Weverything, G​CC 300+ warnings; Unit tests (doctest) (every push / PR): every public A​PI function every exception id and message all Unicode code points single header and split headers custom number, string, array, and object types; Coverage (every push / PR): unit tests cover all lines of the code base; Static analysis (every push / PR, scheduled): Clang-Tidy, Cppcheck, Infer Clang Static Analyzer (52 checkers), I​WYU, Codacy CodeQL, Semgrep, Flawfinder (also scheduled); Dynamic analysis (every push / PR): runtime assertions Valgrind (Memcheck) address and undefined behavior sanitizers, integer overflow, nullability violations; Fuzzing (every PR, continuous): CIFuzz on every pull request OSS-Fuzz: billions of inputs every crash fixed with a reproducing unit test; Style (every push / PR): Artistic Style (astyle), enforced in the C​I cpplint with 61 rules; Docs and examples (every push / PR, scheduled): every documentation example is tested; changed output fails site built with warnings as errors; links checked regularly (scheduled); Integration, supply chain (every push / PR, scheduled): CMake 3.5, 3.31.6, and 4.0.0 Meson, Swift Package Manager dependency review on every PR Dependabot daily, OpenSSF Scorecard, R​EUSE. When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.</desc>
|
||||
<defs><marker id="nj-qa-arrow-n" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="336" height="1626" rx="12"/>
|
||||
<rect class="nj-exceptions-groupbox" x="12" y="70" width="336" height="1572" rx="12"/>
|
||||
<rect class="nj-exceptions-stage" x="65" y="8" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="30">push or pull request</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="42" x2="180" y2="68" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<text class="nj-exceptions-group" x="26" y="94">checks, run in parallel</text>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="308" height="238" rx="8"/>
|
||||
<rect class="nj-exceptions-box" x="26" y="110" width="308" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="135">Build matrix</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="146" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="160">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="156.5" y="146" width="91.6" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="202.3" y="160">before merge</text>
|
||||
<text class="nj-exceptions-line" x="40" y="188">50+ compilers: G​CC, Clang, M​SVC,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="206">AppleClang, Intel, NVIDIA, ...</text>
|
||||
<text class="nj-exceptions-line" x="40" y="224">C++11, 14, 17, 20, 23, and 26</text>
|
||||
<text class="nj-exceptions-line" x="40" y="242">libc++ and libstdc++</text>
|
||||
<text class="nj-exceptions-line" x="40" y="260">warnings: Clang -Weverything,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="278">G​CC 300+ warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="296">a subset on every push and PR,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="314">all before merge; Intel, NVIDIA,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="332">C​UDA, and ARM64 nightly</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="360" width="308" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="385">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="396" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="410">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">every public A​PI function</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">every exception id and message</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">all Unicode code points</text>
|
||||
<text class="nj-exceptions-line" x="40" y="492">single header and split headers</text>
|
||||
<text class="nj-exceptions-line" x="40" y="510">custom number, string, array,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="528">and object types</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="306" width="308" height="184" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="331">Unit tests (doctest)</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="342" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="356">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="384">every public A​PI function</text>
|
||||
<text class="nj-exceptions-line" x="40" y="402">every exception id and message</text>
|
||||
<text class="nj-exceptions-line" x="40" y="420">all Unicode code points</text>
|
||||
<text class="nj-exceptions-line" x="40" y="438">single header and split headers</text>
|
||||
<text class="nj-exceptions-line" x="40" y="456">custom number, string, array,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="474">and object types</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="556" width="308" height="112" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="581">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="40" y="592" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="70.05" y="606">nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="634">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="40" y="652">of the code base</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="502" width="308" height="112" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="527">Coverage</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="538" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="552">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="580">unit tests cover all lines</text>
|
||||
<text class="nj-exceptions-line" x="40" y="598">of the code base</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="680" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="705">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="716" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="730">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="716" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="730">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="758">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="776">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="794">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="812">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="830">(also scheduled); Infer nightly</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="626" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="651">Static analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="662" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="676">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="662" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="676">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="704">Clang-Tidy, Cppcheck, Infer</text>
|
||||
<text class="nj-exceptions-line" x="40" y="722">Clang Static Analyzer (52</text>
|
||||
<text class="nj-exceptions-line" x="40" y="740">checkers), I​WYU, Codacy</text>
|
||||
<text class="nj-exceptions-line" x="40" y="758">CodeQL, Semgrep, Flawfinder</text>
|
||||
<text class="nj-exceptions-line" x="40" y="776">(also scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="858" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="883">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="894" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="908">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="894" width="60.1" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="186.55" y="908">nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="936">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="40" y="954">Valgrind (Memcheck), nightly</text>
|
||||
<text class="nj-exceptions-line" x="40" y="972">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="40" y="990">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1008">nullability violations</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="804" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="829">Dynamic analysis</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="840" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="854">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="882">runtime assertions</text>
|
||||
<text class="nj-exceptions-line" x="40" y="900">Valgrind (Memcheck)</text>
|
||||
<text class="nj-exceptions-line" x="40" y="918">address and undefined behavior</text>
|
||||
<text class="nj-exceptions-line" x="40" y="936">sanitizers, integer overflow,</text>
|
||||
<text class="nj-exceptions-line" x="40" y="954">nullability violations</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1036" width="308" height="148" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1061">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1072" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="73.2" y="1086">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="112.4" y="1072" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="151.9" y="1086">continuous</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1114">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1132">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1150">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1168">reproducing unit test</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="982" width="308" height="148" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1007">Fuzzing</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1018" width="66.4" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="73.2" y="1032">every PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--cont" x="112.4" y="1018" width="79" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--cont" x="151.9" y="1032">continuous</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1060">CIFuzz on every pull request</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1078">OSS-Fuzz: billions of inputs</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1096">every crash fixed with a</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1114">reproducing unit test</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1196" width="308" height="130" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1221">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1232" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1246">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1274">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1292">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1310">cpplint with 61 rules</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1142" width="308" height="130" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1167">Style</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1178" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1192">every push / PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1220">Artistic Style (astyle),</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1238">enforced in the C​I</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1256">cpplint with 61 rules</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1338" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1363">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1374" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1388">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1374" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1388">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1416">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1434">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1452">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1470">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1488">regularly (scheduled)</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1284" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1309">Docs and examples</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1320" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1334">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1320" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1334">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1362">every documentation example is</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1380">tested; changed output fails</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1398">site built with warnings</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1416">as errors; links checked</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1434">regularly (scheduled)</text>
|
||||
</g>
|
||||
<g class="nj-exceptions-card">
|
||||
<rect class="nj-exceptions-box" x="26" y="1516" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1541">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1552" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1566">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1552" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1566">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1594">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1612">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1630">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1648">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1666">Scorecard, R​EUSE</text>
|
||||
<rect class="nj-exceptions-box" x="26" y="1462" width="308" height="166" rx="8"/>
|
||||
<text class="nj-exceptions-ctitle" x="40" y="1487">Integration, supply chain</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--push" x="40" y="1498" width="110.5" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--push" x="95.25" y="1512">every push / PR</text>
|
||||
<rect class="nj-exceptions-badge nj-exceptions-badge--sched" x="156.5" y="1498" width="72.7" height="20" rx="10"/>
|
||||
<text class="nj-exceptions-badgetext nj-exceptions-badgetext--sched" x="192.85" y="1512">scheduled</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1540">CMake 3.5, 3.31.6, and 4.0.0</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1558">Meson, Swift Package Manager</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1576">dependency review on every PR</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1594">Dependabot daily, OpenSSF</text>
|
||||
<text class="nj-exceptions-line" x="40" y="1612">Scorecard, R​EUSE</text>
|
||||
</g>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1696" x2="180" y2="1722" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="65" y="1724" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1746">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1758" x2="180" y2="1784" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="30" y="1786" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1808">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="180" y="1824">no open vulnerability alert</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1642" x2="180" y2="1668" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="65" y="1670" width="230" height="34" rx="17"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1692">merge into develop</text>
|
||||
<line class="nj-exceptions-edge" x1="180" y1="1704" x2="180" y2="1730" marker-end="url(#nj-qa-arrow-n)"/>
|
||||
<rect class="nj-exceptions-stage nj-exceptions-stage--merge" x="30" y="1732" width="300" height="46" rx="23"/>
|
||||
<text class="nj-exceptions-stagetext" x="180" y="1754">release</text>
|
||||
<text class="nj-exceptions-stagesub" x="180" y="1770">no open vulnerability alert</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
@@ -11,6 +11,11 @@ that cost, ordered by how much they typically save.
|
||||
16.2.0 on macOS (Apple M1 Max). All configurations were run interleaved, in alternating order. They show the order
|
||||
of magnitude to expect; measure your own code before and after a change.
|
||||
|
||||
The two sections on `std::format` and ranges apply to C++20 only and were measured with `-std=c++20`, so their
|
||||
baselines are slower than the others. Their differences are mostly within the run-to-run noise of a few percent:
|
||||
two complete runs of the same configuration differed by up to 10% in the median. The tables show the later run (one
|
||||
outlier cell was repeated).
|
||||
|
||||
The translation units are listed below. They replace the ones used for earlier versions of this page, whose
|
||||
sources were not published, so the numbers differ from earlier versions.
|
||||
|
||||
@@ -66,7 +71,8 @@ that cost, ordered by how much they typically save.
|
||||
--8<-- "images/compile_times.svg"
|
||||
</div>
|
||||
<p class="nj-charts-caption" markdown>Change in compile time at `-O0` against the default configuration of the same
|
||||
translation unit, taken from the tables below (medians of nine runs, see above). * computed from the milliseconds in
|
||||
translation unit, taken from the tables below (medians of nine runs, see above; the `std::format` and ranges rows are
|
||||
measured with `-std=c++20`). * computed from the milliseconds in
|
||||
[Options without effect on compile times](#options-without-effect-on-compile-times).</p>
|
||||
|
||||
## Include `json_fwd.hpp` in headers
|
||||
@@ -214,6 +220,88 @@ from and to `std::string`; whether that fallback exists depends on the platform'
|
||||
(`std::wstring` on Windows), so check code that converts paths. `JSON_NO_IO` additionally removes all stream
|
||||
functions (see [Options without effect](#options-without-effect-on-compile-times)).
|
||||
|
||||
## Switch off `std::format` support
|
||||
|
||||
With C++20 and a standard library that provides `<format>`, the library includes it to provide a
|
||||
[`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) specialization (see
|
||||
[`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md)). If you do not format JSON values with `std::format`,
|
||||
switch the detection off for the whole project:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_STD_FORMAT=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | `std::format` off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|----------------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1269 ms / 1539 ms | −1% / 0% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 1944 ms / 2416 ms | −4% / −3% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1347 ms / 2300 ms | 0% / +1% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2644 ms / 4287 ms | −3% / −1% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_STD_FORMAT=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The saving is small, and only GCC shows it: libstdc++'s `<format>` is an additional header that nothing else in the
|
||||
translation unit includes, so the macro removes it (and the headers it pulls in). With libc++ (Apple clang), other
|
||||
standard headers include `<format>` anyway, so the macro only removes the library's own formatter, and the difference is
|
||||
within the noise.
|
||||
|
||||
The trade-off: the `std::formatter<basic_json>` specialization is removed, so `#!cpp std::format("{}", j)`,
|
||||
and `#!cpp std::format_to` with a JSON value no longer compile (in a test with both compilers on
|
||||
macOS, the error was that `std::formatter` must be specialized for the type). Use [`dump()`](../api/basic_json/dump.md),
|
||||
or the stream operators, instead.
|
||||
|
||||
## Switch off ranges support
|
||||
|
||||
With C++20 and a standard library that provides `<ranges>`, the library includes it to declare the
|
||||
[`items()`](../api/basic_json/items.md) proxy a *borrowed range*, and uses the `std::ranges` concepts and algorithms for
|
||||
construction from range views and for iterator input (see [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md)). If you
|
||||
do not need these, switch the detection off for the whole project:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_RANGES=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | Ranges off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|---------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1237 ms / 1495 ms | −4% / −3% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 2004 ms / 2464 ms | −1% / −1% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1311 ms / 2284 ms | −3% / 0% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2705 ms / 4427 ms | 0% / +2% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_RANGES=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The saving is small as well, and with GCC it is not distinguishable from the noise: its `<format>` includes `<span>`,
|
||||
so with `std::format` support left on, part of the ranges machinery is still parsed.
|
||||
Both macros together save more than either alone:
|
||||
|
||||
```cmake
|
||||
target_compile_definitions(my_target PRIVATE JSON_HAS_STD_FORMAT=0 JSON_HAS_RANGES=0)
|
||||
```
|
||||
|
||||
| Compiler | Translation unit | Default (`-O0` / `-O2`) | Both off (`-O0` / `-O2`) | Change |
|
||||
|-------------|------------------|------------------------:|-------------------------:|-----------:|
|
||||
| Apple clang | model | 1283 ms / 1544 ms | 1223 ms / 1504 ms | −5% / −3% |
|
||||
| GCC 16 | model | 2026 ms / 2482 ms | 1914 ms / 2378 ms | −6% / −4% |
|
||||
| Apple clang | parsing | 1350 ms / 2278 ms | 1311 ms / 2271 ms | −3% / 0% |
|
||||
| GCC 16 | parsing | 2715 ms / 4351 ms | 2635 ms / 4260 ms | −3% / −2% |
|
||||
|
||||
<p class="nj-table-caption" markdown>Compile time with `JSON_HAS_STD_FORMAT=0` and `JSON_HAS_RANGES=0` against the default configuration, both with `-std=c++20`, at `-O0` and `-O2`.</p>
|
||||
|
||||
The trade-offs of `JSON_HAS_RANGES=0` are larger than those of the other options on this page:
|
||||
|
||||
- `items()` is no longer a borrowed range. On a temporary, `#!cpp std::ranges::begin(make_json().items())` no longer
|
||||
compiles, and `std::ranges` algorithms such as `#!cpp std::ranges::find_if(make_json().items(), pred)` return
|
||||
`std::ranges::dangling` instead of an iterator, so code that uses the result no longer compiles. Calling them on
|
||||
`#!cpp j.items()` for a named `j` is not affected.
|
||||
- [`JSON_HAS_RANGE_VIEW_CONVERSION`](../api/macros/json_has_range_view_conversion.md) becomes `0`, so a JSON array can no
|
||||
longer be constructed from a `std::ranges::view` such as the result of `std::views::filter`. The assignment
|
||||
`#!cpp json j = v | std::views::filter(pred);` no longer compiles.
|
||||
- Parsing from an iterator pair loses the C++20 fast path for contiguous iterators that are not pointers (for
|
||||
example those of `std::vector`) and falls back to byte-at-a-time scanning. The result is the same; parsing may be
|
||||
slower (not measured here).
|
||||
|
||||
The first two were checked with both compilers on macOS; the third is read from the library's source.
|
||||
|
||||
## Use C++20 modules
|
||||
|
||||
With a toolchain that supports named modules, `import nlohmann.json;` compiles the library once into a module and
|
||||
@@ -343,6 +431,8 @@ and −2% / 0% (GCC). To get a saving, combine it with
|
||||
|
||||
- [`JSON_HAS_FILESYSTEM`](../api/macros/json_has_filesystem.md) - control the conversions from and to
|
||||
`std::filesystem::path`
|
||||
- [`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md) - control the `std::formatter` specialization
|
||||
- [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) - control the use of `std::ranges`
|
||||
- [`JSON_NO_AUTOMATIC_UDLS`](../api/macros/json_no_automatic_udls.md) - do not include the user-defined string
|
||||
literals automatically
|
||||
- [Modules](../features/modules.md) - C++20 module support
|
||||
|
||||
@@ -130,6 +130,7 @@ nav:
|
||||
- features/element_access/unchecked_access.md
|
||||
- features/element_access/checked_access.md
|
||||
- features/element_access/default_value.md
|
||||
- features/files.md
|
||||
- features/iterators.md
|
||||
- features/merge_patch.md
|
||||
- features/json_patch.md
|
||||
@@ -142,6 +143,7 @@ nav:
|
||||
- features/performance.md
|
||||
- Parsing:
|
||||
- features/parsing/index.md
|
||||
- features/parsing/error_recovery.md
|
||||
- features/parsing/json_lines.md
|
||||
- features/parsing/large_and_partial_input.md
|
||||
- features/parsing/parse_exceptions.md
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
wheel==0.48.0
|
||||
|
||||
mkdocs==1.6.1 # documentation framework
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.0 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.7.7 # theme for mkdocs
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.2 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.8.0 # theme for mkdocs
|
||||
mkdocs-material-extensions==1.3.1 # extensions
|
||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||
|
||||
@@ -73,6 +73,8 @@ COMPILE = [
|
||||
("JSON_NO_AUTOMATIC_UDLS", "parsing TU", -1, -2, False),
|
||||
("JSON_HAS_FILESYSTEM=0 + JSON_NO_IO", "model TU", -8, -11, False),
|
||||
("JSON_HAS_FILESYSTEM=0", "model TU", -5, -9, False),
|
||||
("JSON_HAS_STD_FORMAT=0", "model TU", -1, -4, False),
|
||||
("JSON_HAS_RANGES=0", "model TU", -4, -1, False),
|
||||
# computed from the ms in "Options without effect on compile times": 1034/1051, 1756/1801, 1055/1051, 1783/1801
|
||||
("JSON_NO_IO", "model TU", round((1034 / 1051 - 1) * 100), round((1756 / 1801 - 1) * 100), True),
|
||||
("JSON_USE_GLOBAL_UDLS=0", "model TU", round((1055 / 1051 - 1) * 100), round((1783 / 1801 - 1) * 100), True),
|
||||
|
||||
@@ -22,24 +22,19 @@ P = "nj-exceptions-"
|
||||
TRIGGERS = {
|
||||
"push": ("every push / PR", "push"),
|
||||
"pr": ("every PR", "push"),
|
||||
"merge": ("before merge", "push"),
|
||||
"nightly": ("nightly", "sched"),
|
||||
"sched": ("scheduled", "sched"),
|
||||
"cont": ("continuous", "cont"),
|
||||
}
|
||||
|
||||
# title, triggers, lines (at most 31 characters, so they fit a card of the three-column drawing)
|
||||
LANES = [
|
||||
("Build matrix", ["push", "merge"], [
|
||||
("Build matrix", ["push"], [
|
||||
"50+ compilers: GCC, Clang, MSVC,",
|
||||
"AppleClang, Intel, NVIDIA, ...",
|
||||
"C++11, 14, 17, 20, 23, and 26",
|
||||
"libc++ and libstdc++",
|
||||
"warnings: Clang -Weverything,",
|
||||
"GCC 300+ warnings",
|
||||
"a subset on every push and PR,",
|
||||
"all before merge; Intel, NVIDIA,",
|
||||
"CUDA, and ARM64 nightly",
|
||||
]),
|
||||
("Unit tests (doctest)", ["push"], [
|
||||
"every public API function",
|
||||
@@ -49,7 +44,7 @@ LANES = [
|
||||
"custom number, string, array,",
|
||||
"and object types",
|
||||
]),
|
||||
("Coverage", ["nightly"], [
|
||||
("Coverage", ["push"], [
|
||||
"unit tests cover all lines",
|
||||
"of the code base",
|
||||
]),
|
||||
@@ -58,11 +53,11 @@ LANES = [
|
||||
"Clang Static Analyzer (52",
|
||||
"checkers), IWYU, Codacy",
|
||||
"CodeQL, Semgrep, Flawfinder",
|
||||
"(also scheduled); Infer nightly",
|
||||
"(also scheduled)",
|
||||
]),
|
||||
("Dynamic analysis", ["push", "nightly"], [
|
||||
("Dynamic analysis", ["push"], [
|
||||
"runtime assertions",
|
||||
"Valgrind (Memcheck), nightly",
|
||||
"Valgrind (Memcheck)",
|
||||
"address and undefined behavior",
|
||||
"sanitizers, integer overflow,",
|
||||
"nullability violations",
|
||||
@@ -183,7 +178,7 @@ def drawing(W, cols, suffix):
|
||||
defs = ('<defs><marker id="%s" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">'
|
||||
'<path class="fg-head" d="M0 1 L10 5 L0 9 z"/></marker></defs>' % mid)
|
||||
tid, did = "nj-qa-title-" + suffix, "nj-qa-desc-" + suffix
|
||||
desc = ("A push or pull request starts these checks; the badges say when each runs: " +
|
||||
desc = ("Every push or pull request starts these checks in parallel: " +
|
||||
"; ".join("%s (%s): %s" % (t, ", ".join(TRIGGERS[k][0] for k in trig), " ".join(lines)) for t, trig, lines in LANES) +
|
||||
". When they pass, the change is merged into develop; a release is made only when no vulnerability alert is open.")
|
||||
out = ['<svg class="nj-fig %sqa %sqa--%s" viewBox="0 0 %d %d" role="img" aria-labelledby="%s %s" xmlns="http://www.w3.org/2000/svg">'
|
||||
|
||||
@@ -91,7 +91,7 @@ const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j)
|
||||
}
|
||||
|
||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/)
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) // LCOV_EXCL_LINE
|
||||
{
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <type_traits> // conditional
|
||||
|
||||
|
||||
// The shortest-digit conversion of doubles (see zmij.hpp) and the writing of
|
||||
// its digits, including the conversion of the digits to text by Xiang JunBo
|
||||
// and the SIMD instruction sequence by Dougall Johnson, are ported from Zmij
|
||||
// by Victor Zverovich (https://github.com/vitaut/zmij, MIT license).
|
||||
//
|
||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
||||
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
||||
|
||||
@@ -198,18 +198,19 @@ class exception : public std::exception
|
||||
// all other parent types (primitives) contribute no token
|
||||
}
|
||||
|
||||
if (tokens.empty())
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string prefix;
|
||||
if (!tokens.empty())
|
||||
{
|
||||
return "";
|
||||
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
prefix = concat('(', str, ") ");
|
||||
}
|
||||
|
||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
|
||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
||||
return concat(prefix, get_byte_positions(leaf_element));
|
||||
#else
|
||||
return get_byte_positions(leaf_element);
|
||||
#endif
|
||||
@@ -222,7 +223,8 @@ class exception : public std::exception
|
||||
template<typename BasicJsonType>
|
||||
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))
|
||||
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||
if (leaf_element != nullptr && 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()), ") ");
|
||||
}
|
||||
@@ -431,6 +433,28 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace view
|
||||
{
|
||||
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
|
||||
template<typename View>
|
||||
std::string diagnostics_of(const View& v);
|
||||
} // namespace view
|
||||
|
||||
// what goes in front of the message of an exception about j: nothing for a
|
||||
// basic_json value (exception_context() passes the value itself), the context
|
||||
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
|
||||
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||
std::string exception_prefix(const View& v)
|
||||
{
|
||||
return view::diagnostics_of(v);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because @a j does not have the type a conversion expects
|
||||
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
||||
@@ -442,7 +466,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
|
||||
{
|
||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -58,6 +58,19 @@
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||
|
||||
// Whether file_input_adapter scans the buffer of the FILE directly, as
|
||||
// getc_unlocked() reads it: the fields it uses are those that the C
|
||||
// library's own getc_unlocked() macro uses (glibc: _IO_read_ptr and
|
||||
// _IO_read_end; Apple: _p and _r). It needs the file locked for the whole
|
||||
// parse, as JSON_UNLOCKED_FILE_IO == 1 has it (so JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
// and MemorySanitizer builds turn it off).
|
||||
// (uClibc defines __GLIBC__ as well, but its FILE has other fields)
|
||||
#if JSON_UNLOCKED_FILE_IO == 1 && ((defined(__GLIBC__) && !defined(__UCLIBC__)) || defined(__APPLE__))
|
||||
#define JSON_FILE_WINDOW 1
|
||||
#else
|
||||
#define JSON_FILE_WINDOW 0
|
||||
#endif
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -179,8 +192,12 @@ class token_carry_buffer
|
||||
char* m_capacity_end = nullptr;
|
||||
};
|
||||
/*!
|
||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||
buffer. This adapter is a very low level adapter.
|
||||
Input adapter for stdio file access. The file is locked for the lifetime of the
|
||||
adapter and read with the unlocked stdio functions (see
|
||||
JSON_DISABLE_UNLOCKED_FILE_IO). With glibc and the Apple C library, the
|
||||
characters in the buffer of the FILE are also scanned in bulk, as the get area
|
||||
of a stream is (see input_stream_adapter): exactly the characters getc_unlocked()
|
||||
would return are consumed, so the file is left where it was left before.
|
||||
*/
|
||||
class file_input_adapter
|
||||
{
|
||||
@@ -199,6 +216,12 @@ class file_input_adapter
|
||||
file_input_adapter(const file_input_adapter&) = delete;
|
||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||
: m_file(rhs.m_file)
|
||||
#if JSON_FILE_WINDOW
|
||||
, token_carry(std::move(rhs.token_carry))
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_buffer(rhs.current_in_buffer)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
#endif
|
||||
{
|
||||
// the lock is owned by the thread, not the adapter: hand it over
|
||||
rhs.m_file = nullptr;
|
||||
@@ -210,6 +233,20 @@ class file_input_adapter
|
||||
unlock();
|
||||
}
|
||||
|
||||
#if JSON_FILE_WINDOW
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
// what getc_unlocked() does, while the buffer has characters
|
||||
const char* const next = buffer_ptr();
|
||||
if (JSON_HEDLEY_LIKELY(next != buffer_end()))
|
||||
{
|
||||
current_in_buffer = true;
|
||||
buffer_bump(1);
|
||||
return std::char_traits<char>::to_int_type(*next);
|
||||
}
|
||||
return get_character_slow();
|
||||
}
|
||||
#else
|
||||
std::char_traits<char>::int_type get_character() noexcept
|
||||
{
|
||||
// the file is locked for the lifetime of the adapter, so the
|
||||
@@ -222,6 +259,7 @@ class file_input_adapter
|
||||
return std::fgetc(m_file);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// returns the number of characters successfully read
|
||||
template<class T>
|
||||
@@ -230,7 +268,134 @@ class file_input_adapter
|
||||
return fread(dest, 1, sizeof(T) * count, m_file);
|
||||
}
|
||||
|
||||
#if JSON_FILE_WINDOW
|
||||
// The lexer may scan the buffer of the FILE directly, as the window of
|
||||
// input_stream_adapter: the buffer ends wherever the C library's buffer
|
||||
// ends, and is refilled by the next get_character().
|
||||
static constexpr bool supports_bulk_scan = true;
|
||||
static constexpr bool bulk_window = true;
|
||||
|
||||
// Pointer to the next unread character; only valid when bulk_remaining() > 0.
|
||||
const char* bulk_data() const
|
||||
{
|
||||
return buffer_ptr();
|
||||
}
|
||||
|
||||
// Number of unread characters in the buffer.
|
||||
std::size_t bulk_remaining() const
|
||||
{
|
||||
if (!current_in_buffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::size_t>(buffer_end() - buffer_ptr());
|
||||
}
|
||||
|
||||
// Consume @a n characters previously inspected via bulk_data().
|
||||
void bulk_skip(std::size_t n)
|
||||
{
|
||||
JSON_ASSERT(n <= bulk_remaining());
|
||||
buffer_bump(n);
|
||||
}
|
||||
|
||||
// The adapter keeps the current token for the lexer's error messages, as
|
||||
// input_stream_adapter does.
|
||||
static constexpr bool supports_token_tracking = true;
|
||||
|
||||
void begin_token() noexcept
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
token_segment = buffer_ptr();
|
||||
}
|
||||
|
||||
void mark_token_start(std::char_traits<char>::int_type c)
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
if (current_in_buffer)
|
||||
{
|
||||
token_segment = buffer_ptr() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_segment = nullptr;
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
token_carry.copy_to(out);
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, buffer_ptr());
|
||||
}
|
||||
if (drop_last && !out.empty())
|
||||
{
|
||||
out.pop_back();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if JSON_FILE_WINDOW
|
||||
// the buffer of the FILE: [buffer_ptr(), buffer_end()) are the characters
|
||||
// getc_unlocked() would return next
|
||||
#if defined(__GLIBC__)
|
||||
const char* buffer_ptr() const noexcept
|
||||
{
|
||||
return m_file->_IO_read_ptr;
|
||||
}
|
||||
const char* buffer_end() const noexcept
|
||||
{
|
||||
return m_file->_IO_read_end;
|
||||
}
|
||||
void buffer_bump(std::size_t n) noexcept
|
||||
{
|
||||
m_file->_IO_read_ptr += n;
|
||||
}
|
||||
#else
|
||||
const char* buffer_ptr() const noexcept
|
||||
{
|
||||
return reinterpret_cast<const char*>(m_file->_p);
|
||||
}
|
||||
const char* buffer_end() const noexcept
|
||||
{
|
||||
return m_file->_r > 0 ? reinterpret_cast<const char*>(m_file->_p) + m_file->_r : reinterpret_cast<const char*>(m_file->_p);
|
||||
}
|
||||
void buffer_bump(std::size_t n) noexcept
|
||||
{
|
||||
m_file->_p += n;
|
||||
m_file->_r -= static_cast<int>(n);
|
||||
}
|
||||
#endif
|
||||
|
||||
// the buffer is exhausted: getc_unlocked() refills it
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.append(token_segment, buffer_end());
|
||||
token_segment = nullptr;
|
||||
}
|
||||
const auto res = getc_unlocked(m_file);
|
||||
if (res == EOF)
|
||||
{
|
||||
current_in_buffer = false;
|
||||
return std::char_traits<char>::eof();
|
||||
}
|
||||
// the character was taken from the refilled buffer, at buffer_ptr() - 1
|
||||
current_in_buffer = true;
|
||||
if (tracking_token)
|
||||
{
|
||||
token_segment = buffer_ptr() - 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Lock the file once for the whole parse instead of once per character.
|
||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||
// same thread keep working.
|
||||
@@ -259,6 +424,16 @@ class file_input_adapter
|
||||
|
||||
/// the file pointer to read from
|
||||
std::FILE* m_file;
|
||||
#if JSON_FILE_WINDOW
|
||||
/// the part of the current token that was in earlier buffers
|
||||
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the buffer, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the buffer
|
||||
bool current_in_buffer = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -545,12 +720,9 @@ class input_stream_adapter
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
// the get area is exhausted (get_character() handles the other case),
|
||||
// so sbumpc() refills it
|
||||
save_token_segment();
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
@@ -563,17 +735,13 @@ class input_stream_adapter
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
else
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
@@ -796,17 +964,22 @@ class iterator_input_adapter
|
||||
return get_consumed_count() + eof_reads;
|
||||
}
|
||||
|
||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
||||
// is set to the index after the last of them (0 if there is none); only
|
||||
// valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
||||
// number of newlines in the consumed range [first_index, last_index); if
|
||||
// there is one, @a line_start is set to the index after the last of them,
|
||||
// otherwise it is left unchanged; only valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t first_index, std::size_t last_index, std::size_t& line_start) const
|
||||
{
|
||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
||||
using difference_type = typename std::iterator_traits<IteratorType>::difference_type;
|
||||
const auto from = std::next(begin, static_cast<difference_type>(first_index));
|
||||
const auto to = std::next(from, static_cast<difference_type>(last_index - first_index));
|
||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(from), newline);
|
||||
if (last_newline.base() != from)
|
||||
{
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
}
|
||||
return static_cast<std::size_t>(std::count(from, last_newline.base(), newline));
|
||||
}
|
||||
|
||||
// append the already-consumed characters in the half-open range
|
||||
|
||||
@@ -138,7 +138,9 @@ struct json_sax
|
||||
@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)
|
||||
@return whether to recover from the error: false stops parsing; true
|
||||
repairs the error and continues, or, if that is not possible,
|
||||
stops after completing the value read so far
|
||||
*/
|
||||
virtual bool parse_error(std::size_t position,
|
||||
const std::string& last_token,
|
||||
@@ -230,7 +232,10 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
// (npos: a value without a source position, see materialize() of
|
||||
// json_view)
|
||||
const std::size_t read = lexer->get_chars_read_total();
|
||||
v.start_position = read == std::string::npos ? std::string::npos : read - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +250,12 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
if (v.end_position == std::string::npos)
|
||||
{
|
||||
// a range has both ends or none (exception::diagnostics()
|
||||
// prints only complete ones)
|
||||
v.start_position = std::string::npos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +272,12 @@ struct diagnostic_positions
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
if (v.end_position == std::string::npos)
|
||||
{
|
||||
// a value without a source position
|
||||
v.start_position = std::string::npos;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
@@ -470,11 +487,18 @@ stored in place as it arrives, so after a parse error the value holds what was
|
||||
read up to it.
|
||||
|
||||
@tparam BasicJsonType the JSON type
|
||||
@tparam InputAdapterType the input adapter of the lexer that can be passed to
|
||||
the constructor to record diagnostic positions; it
|
||||
does not matter if no lexer is passed
|
||||
@tparam Collect whether to collect the values of containers of unknown size on
|
||||
a stack (only for a caller that does not use the value after a
|
||||
parse error)
|
||||
@tparam LexerType the type of the lexer that reports the source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses
|
||||
it, so any type with its three functions works (as
|
||||
materialize() of json_view uses it)
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false>
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
@@ -483,7 +507,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
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<BasicJsonType, InputAdapterType>;
|
||||
using lexer_t = LexerType;
|
||||
|
||||
/*!
|
||||
@param[in,out] r reference to a JSON value that is manipulated while
|
||||
@@ -780,6 +804,8 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void grow()
|
||||
{
|
||||
// the stack starts with local_capacity frames and only grows
|
||||
JSON_ASSERT(capacity != 0);
|
||||
const std::size_t new_capacity = capacity * 2;
|
||||
std::unique_ptr<frame[]> new_heap(new frame[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
@@ -885,7 +911,8 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
hashes.push_back(hasher(element.first));
|
||||
}
|
||||
ordered_data.key_indexes.emplace_back(new object_key_index(std::move(hashes)));
|
||||
std::unique_ptr<object_key_index> index(new object_key_index(std::move(hashes))); // NOLINT(cppcoreguidelines-owning-memory,modernize-make-unique): std::make_unique is C++14
|
||||
ordered_data.key_indexes.push_back(std::move(index));
|
||||
f.ordered.index = ordered_data.key_indexes.back().get();
|
||||
}
|
||||
|
||||
@@ -980,7 +1007,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
ordered_data.members[f.ordered.dest_member].second = std::move(value);
|
||||
}
|
||||
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {}
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {} // LCOV_EXCL_LINE
|
||||
|
||||
/// store the members of a new object directly in it
|
||||
bool start_stored_object(std::size_t len)
|
||||
@@ -1284,7 +1311,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
// a new member is null and holds nothing: construct the value
|
||||
// over it instead of destroying and swapping
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible<BasicJsonType, Value> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1293,6 +1320,26 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
}
|
||||
}
|
||||
|
||||
/// constructing the value cannot throw (for example a number or a boolean): construct it in place
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||
{
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
}
|
||||
|
||||
/// constructing the value can throw (for example a container or a string), and a
|
||||
/// constructor that was interrupted must not have touched the member (#5985):
|
||||
/// create the value first, then move it over the member (the move constructor is
|
||||
/// noexcept)
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||
{
|
||||
BasicJsonType value(std::forward<Value>(v));
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||
}
|
||||
|
||||
/// a custom base class may care about the lifetime of the value
|
||||
template<typename Value>
|
||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||
@@ -1595,7 +1642,7 @@ class json_sax_dom_binary_parser : public json_sax_dom_error_state
|
||||
lexer_t* m_lexer_ref = nullptr;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
@@ -1718,7 +1765,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
key_stack.push_back(val);
|
||||
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
if (keep)
|
||||
{
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
// a single lookup: insert the placeholder, or find the existing member
|
||||
@@ -1775,7 +1822,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
|
||||
const string_t object_key = pop_container();
|
||||
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
{
|
||||
// remove discarded value
|
||||
remove_discarded_value(*ref_stack.back(), object_key);
|
||||
@@ -1941,8 +1988,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
*/
|
||||
string_t current_key() const
|
||||
{
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
||||
&& !key_stack.empty())
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
{
|
||||
return key_stack.back();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -44,6 +44,13 @@
|
||||
// use std::from_chars or std::strtold. They are free functions so the lexer
|
||||
// stays focused on scanning (see lexer::convert_number()) and so that other
|
||||
// parsers of JSON text can convert tokens exactly like it does.
|
||||
//
|
||||
// The Eisel-Lemire conversion (eisel_lemire()), the digit comparison for the
|
||||
// cases it cannot decide, and the eight-digits-at-a-time helpers
|
||||
// (is_eight_digits(), parse_eight_digits()) are adapted from fast_float
|
||||
// (https://github.com/fastfloat/fast_float, Daniel Lemire and contributors,
|
||||
// available under the MIT, Apache 2.0, and Boost Software licenses; used here
|
||||
// under the MIT license). The table of powers of five is in pow5_table.hpp.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
|
||||
@@ -56,8 +56,8 @@ using parser_callback_t =
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
@@ -72,14 +72,14 @@ This class implements a parser for JSON text. Nested arrays and objects are trac
|
||||
stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read
|
||||
is reported as SAX events.
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool DiscardValues = false>
|
||||
class parser
|
||||
{
|
||||
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 lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType, DiscardValues>;
|
||||
using token_type = typename lexer_t::token_type;
|
||||
|
||||
public:
|
||||
@@ -88,10 +88,9 @@ class parser
|
||||
parser_callback_t<BasicJsonType> cb = nullptr,
|
||||
const bool allow_exceptions_ = true,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas_ = false,
|
||||
const bool discard_number_values_ = false)
|
||||
const bool ignore_trailing_commas_ = false)
|
||||
: callback(std::move(cb))
|
||||
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
||||
, m_lexer(std::move(adapter), ignore_comments)
|
||||
, allow_exceptions(allow_exceptions_)
|
||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||
{
|
||||
@@ -153,25 +152,72 @@ class parser
|
||||
bool accept(const bool strict = true)
|
||||
{
|
||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||
return sax_parse(&sax_acceptor, strict);
|
||||
return sax_parse_impl<false>(&sax_acceptor, strict);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief public SAX interface
|
||||
|
||||
If the SAX parser's parse_error() returns true, the parser recovers from
|
||||
the error: it repairs the input and continues (see #3989).
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] strict whether to expect the last token to be EOF
|
||||
@return whether the input was parsed without errors and no SAX event
|
||||
returned false
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse(SAX* sax, const bool strict = true)
|
||||
{
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
if (result)
|
||||
{
|
||||
return finish_input(*sax, strict);
|
||||
}
|
||||
|
||||
return false;
|
||||
return sax_parse_impl<true>(sax, strict);
|
||||
}
|
||||
|
||||
private:
|
||||
/// what sax_parse_internal() does after an object key was expected
|
||||
enum class next_step : std::uint8_t
|
||||
{
|
||||
/// stop parsing
|
||||
stop,
|
||||
/// parse a value that begins with last_token
|
||||
parse_value,
|
||||
/// evaluate the state of the innermost container, which reads
|
||||
/// last_token again
|
||||
evaluate_state
|
||||
};
|
||||
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_impl(SAX* sax, const bool strict)
|
||||
{
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF; after recovering from an
|
||||
// error, the end of the input may already have been read
|
||||
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||
{
|
||||
// the value is complete, so there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||
std::integral_constant<bool, AllowRecovery> {}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
return result && !error_reported;
|
||||
}
|
||||
|
||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||
template<typename SAX>
|
||||
const string_t& number_float_text(SAX* /*sax*/)
|
||||
@@ -239,17 +285,26 @@ class parser
|
||||
template<typename DomSax>
|
||||
bool parse_dom(DomSax& sdp, const bool strict)
|
||||
{
|
||||
sax_parse_internal(&sdp);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
|
||||
static_cast<void>(finish_input(sdp, strict));
|
||||
|
||||
return !sdp.is_errored();
|
||||
}
|
||||
|
||||
template<typename SAX>
|
||||
/*!
|
||||
@brief parse a JSON value and pass it to a SAX parser
|
||||
|
||||
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
||||
parse_error() returns true; false for the SAX parsers
|
||||
of parse() and accept(), which never do, so that no
|
||||
code for recovering is generated for them
|
||||
*/
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_internal(SAX* sax)
|
||||
{
|
||||
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
||||
// value to avoid a goto (see comment where set to true)
|
||||
bool skip_to_state_evaluation = false;
|
||||
|
||||
@@ -288,10 +343,18 @@ class parser
|
||||
break;
|
||||
}
|
||||
|
||||
// parse key
|
||||
// remember we are now inside an object
|
||||
states.push_back(0);
|
||||
|
||||
// parse key (the steps of parse_key(), which are
|
||||
// repeated here and below for speed)
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
@@ -301,12 +364,13 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(0);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
continue;
|
||||
@@ -342,9 +406,11 @@ class parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
if (!overflow_error(sax, res, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||
@@ -412,16 +478,63 @@ class parser
|
||||
case token_type::parse_error:
|
||||
{
|
||||
// using "uninitialized" to avoid an "expected" message
|
||||
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: keep what can be read of the token
|
||||
recover_token(allow_recovery);
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
// a string or a number
|
||||
continue;
|
||||
}
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// nothing could be read
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
return syntax_error(*sax, empty_input_message());
|
||||
// there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(),
|
||||
empty_input_message(), nullptr), allow_recovery));
|
||||
return false;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: the input ends where a value is missing
|
||||
if (states.empty())
|
||||
{
|
||||
// there is no value
|
||||
return false;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
@@ -431,7 +544,35 @@ class parser
|
||||
case token_type::literal_or_value:
|
||||
default: // the last token was unexpected
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (last_token == token_type::name_separator)
|
||||
{
|
||||
// a stray ':'; the value may follow
|
||||
get_token();
|
||||
continue;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,7 +623,30 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the array
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_object)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
// otherwise, a missing ',' (or a stray ':', which value
|
||||
// parsing drops): the next value begins here
|
||||
continue;
|
||||
}
|
||||
|
||||
// states.back() is false -> object
|
||||
@@ -499,9 +663,12 @@ class parser
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
@@ -510,7 +677,11 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse values
|
||||
@@ -538,10 +709,546 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the object
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_array)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief continue sax_parse_internal() after a recovery
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
||||
{
|
||||
if (step == next_step::evaluate_state)
|
||||
{
|
||||
// the state evaluation reads the token again
|
||||
m_lexer.unget_token();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
return step != next_step::stop;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers: stop parsing
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief parse an object key and the name separator (:) after it
|
||||
|
||||
last_token is the token where the key is expected. sax_parse_internal()
|
||||
repeats these steps rather than calling this function, which is used
|
||||
when recovering from an error.
|
||||
|
||||
@return next_step::parse_value if the value follows, with last_token its
|
||||
first token; next_step::evaluate_state if the object's state is
|
||||
to be evaluated after recovering from an error; next_step::stop
|
||||
to stop parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step parse_key(SAX* sax)
|
||||
{
|
||||
const std::true_type allow_recovery{};
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return key_error(sax, allow_recovery, false);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return key_error(sax, allow_recovery, true);
|
||||
}
|
||||
|
||||
// the value begins with the next token
|
||||
get_token();
|
||||
return next_step::parse_value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a number that is too large for number_float_t, and recover
|
||||
from the error by passing the value on; the SAX parser gets the
|
||||
number's text as well
|
||||
|
||||
This is a separate function, as reading other numbers is measurably
|
||||
slower if the error is handled where they are read.
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] value the value that is not finite
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool overflow_error(SAX* sax, const number_float_t value, std::true_type allow_recovery)
|
||||
{
|
||||
if (!report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return sax->number_float(value, number_float_text(sax));
|
||||
}
|
||||
|
||||
/// @copydoc overflow_error
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type overflow_error(SAX* sax, const number_float_t /*value*/, std::false_type allow_recovery)
|
||||
{
|
||||
return report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key; the parser for parse() and accept() never recovers
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
@return std::false_type, see report_error()
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
||||
{
|
||||
return report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), key_read
|
||||
? exception_message(token_type::name_separator, "object separator")
|
||||
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key, and recover from it
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
||||
{
|
||||
if (!key_read)
|
||||
{
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_key(sax);
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_name_separator(sax);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// error recovery
|
||||
/////////////////////
|
||||
|
||||
/*
|
||||
The functions below repair an error after the SAX parser's parse_error()
|
||||
returned true (see #3989). Each mistake is repaired by the smallest local
|
||||
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
||||
be read of an invalid string or number is kept (see
|
||||
lexer::recover_token()), a missing value becomes null, a wrong closing
|
||||
bracket closes the innermost container, and the end of the input closes
|
||||
all of them. The events stay balanced, and every key() is followed by
|
||||
exactly one value.
|
||||
|
||||
A repair hands a token to the state evaluation, by returning it to the
|
||||
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
||||
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
||||
hands a token to value or key parsing only if it is none of them, so a
|
||||
token is never handed back and forth. Every other step reads a token or
|
||||
closes a container, so parsing always ends.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser; the parser for parse() and
|
||||
accept() never recovers
|
||||
|
||||
@return std::false_type rather than false: its value is known where the
|
||||
function is called even if the call is not inlined, so the code
|
||||
for recovering is not generated
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
error_reported = true;
|
||||
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser
|
||||
@return whether to recover from the error
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
const std::size_t position = m_lexer.get_position().chars_read_total;
|
||||
if (error_reported && position == last_error_position && last_token == last_error_token)
|
||||
{
|
||||
// a repair handed on the token of the error it repaired; the
|
||||
// token was reported already, and the SAX parser asked to recover
|
||||
return true;
|
||||
}
|
||||
|
||||
error_reported = true;
|
||||
last_error_position = position;
|
||||
last_error_token = last_token;
|
||||
|
||||
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the token string of the next error begins here
|
||||
m_lexer.restart_token_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief keep what can be read of the token that the lexer rejected
|
||||
|
||||
The error was reported for the rejected token, so it is not reported again
|
||||
for the token it is repaired to (see lexer::recover_token()).
|
||||
*/
|
||||
token_type recover_token()
|
||||
{
|
||||
last_token = m_lexer.recover_token();
|
||||
last_error_position = m_lexer.get_position().chars_read_total;
|
||||
last_error_token = last_token;
|
||||
return last_token;
|
||||
}
|
||||
|
||||
// The functions below are called from sax_parse_internal() after an error
|
||||
// was reported. The parser for parse() and accept() stopped there, so for
|
||||
// it they are stubs: the code for recovering is then not even referenced,
|
||||
// and unoptimized builds do not emit it either.
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
token_type recover_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
return recover_token();
|
||||
}
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_token(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// return the token that was read last to the lexer (see lexer::unget_token())
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
void unget_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
m_lexer.unget_token();
|
||||
}
|
||||
|
||||
/// @copydoc unget_token(std::true_type)
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static void unget_token(std::false_type /*allow_recovery*/) noexcept {}
|
||||
|
||||
/// @copydoc skip_to_value
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type skip_to_value(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// @copydoc recover_missing_value
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_missing_value(SAX* /*sax*/, const std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// pass the end events of all open containers
|
||||
template<typename SAX>
|
||||
bool close_containers(SAX* sax, std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (!states.empty())
|
||||
{
|
||||
const bool is_array = states.back() != 0;
|
||||
states.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @copydoc close_containers
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type close_containers(SAX* /*sax*/, std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read tokens until one begins a value, skipping everything before
|
||||
the top-level value
|
||||
@return whether a value begins with last_token
|
||||
*/
|
||||
bool skip_to_value(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (get_token())
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::literal_true:
|
||||
case token_type::value_float:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
return true;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return false;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::value_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip the rest of an object member that cannot be read
|
||||
|
||||
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
||||
not inside a container that begins in the skipped tokens, or the end of
|
||||
the input.
|
||||
*/
|
||||
void skip_member()
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
while (true)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
++depth;
|
||||
break;
|
||||
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
--depth;
|
||||
break;
|
||||
|
||||
case token_type::value_separator:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
get_token();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a value where it is missing
|
||||
|
||||
last_token is ',', ']', '}', or the end of the input, where a value was
|
||||
expected. In an object, the key gets null; in an array, a ',' where a
|
||||
value is missing stands for null (as in JavaScript), while an array that
|
||||
ends there just ends.
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool recover_missing_value(SAX* sax, const std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
JSON_ASSERT(!states.empty());
|
||||
if (states.back() == 0 || last_token == token_type::value_separator)
|
||||
{
|
||||
return sax->null();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// recover from a missing key; last_token is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_key(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// no member: the object's state handles the token
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a key that could be repaired
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a member without a key
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a missing name separator (:) after the key; last_token
|
||||
/// is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_name_separator(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// the value is missing as well
|
||||
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::parse_error:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a missing ':'; the value begins here
|
||||
return next_step::parse_value;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a token after an object member that is neither ',' nor
|
||||
/// '}' (nor ']' or the end of the input, which the caller handles)
|
||||
template<typename SAX>
|
||||
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
recover_token();
|
||||
}
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a missing ','; the next key begins here
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers (and does not come
|
||||
/// here, as report_error() returned false)
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// get next token from lexer
|
||||
token_type get_token()
|
||||
{
|
||||
@@ -574,14 +1281,7 @@ class parser
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
if (!context.empty())
|
||||
{
|
||||
error_msg += concat("while parsing ", context, ' ');
|
||||
}
|
||||
|
||||
error_msg += "- ";
|
||||
std::string error_msg = concat("syntax error while parsing ", context, " - ");
|
||||
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
@@ -599,7 +1299,7 @@ class parser
|
||||
}
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
private:
|
||||
/// callback function
|
||||
@@ -612,6 +1312,12 @@ class parser
|
||||
const bool allow_exceptions = true;
|
||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||
const bool ignore_trailing_commas = false;
|
||||
/// whether an error was reported to the SAX parser
|
||||
bool error_reported = false;
|
||||
/// the position of the last reported error
|
||||
std::size_t last_error_position = 0;
|
||||
/// the token of the last reported error
|
||||
token_type last_error_token = token_type::uninitialized;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
||||
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
||||
// without disabling the SSE2 code.
|
||||
//
|
||||
// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson
|
||||
// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John
|
||||
// Keiser, and contributors, available under the Apache 2.0 and MIT licenses;
|
||||
// used here under the MIT license): the "lookup4" algorithm and tables of
|
||||
// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per
|
||||
// Byte" (2021).
|
||||
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#include <arm_neon.h>
|
||||
#define JSON_SCAN_NEON 1
|
||||
@@ -315,6 +322,78 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
block_out = block;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns the start of the first block in which the check
|
||||
found an error (which may belong to a sequence begun in the block before) or
|
||||
where fewer than 16 bytes are left; a sequence may be cut at that point, so the
|
||||
caller resumes at its start, one sequence at a time.
|
||||
*/
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
||||
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||
uint8x16_t prev = vdupq_n_u8(0);
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80)
|
||||
{
|
||||
// ASCII here, and no sequence from the block before to finish
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||
if (vmaxvq_u8(err) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#else
|
||||
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
__m128i prev = zero;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0)
|
||||
{
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||
if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -90,6 +90,34 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
||||
return n;
|
||||
}
|
||||
|
||||
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||
if (zero != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first zero byte (see
|
||||
// find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (data[i] == 0x00)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// classify a byte as one the serializer must NOT copy verbatim when
|
||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||
@@ -236,6 +264,64 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
||||
return pos;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether [data, data+n) is valid UTF-8, for checking whole strings
|
||||
|
||||
The same answer as valid_utf8_prefix(data, n) == n. Short ASCII strings, the
|
||||
most common keys and values, are decided in one test, and long strings are
|
||||
checked 16 bytes at a time where the vector check is available.
|
||||
*/
|
||||
inline bool valid_utf8_string(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
if (n <= 16)
|
||||
{
|
||||
// two overlapping words cover 8 to 16 bytes, one partial word fewer
|
||||
std::uint64_t first = 0;
|
||||
std::uint64_t last = 0;
|
||||
if (n >= 8)
|
||||
{
|
||||
std::memcpy(&first, data, sizeof(first));
|
||||
std::memcpy(&last, data + n - 8, sizeof(last));
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
std::memcpy(&first, data, n);
|
||||
}
|
||||
if (((first | last) & high) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
else if (n >= 32
|
||||
#if JSON_SCAN_SSSE3_DISPATCH
|
||||
&& cpu_has_ssse3()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
const unsigned char* block = vector_utf8_valid_blocks(data, data + n);
|
||||
// the rest from the start of a sequence that may be cut at block
|
||||
for (int i = 1; i <= 3 && block - i >= data; ++i)
|
||||
{
|
||||
const unsigned char c = block[-i];
|
||||
if (c < 0x80)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (c >= 0xC0)
|
||||
{
|
||||
block -= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto checked = static_cast<std::size_t>(block - data);
|
||||
return checked + valid_utf8_prefix(block, n - checked) == n;
|
||||
}
|
||||
#endif
|
||||
return valid_utf8_prefix(data, n) == n;
|
||||
}
|
||||
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
/// the end of scan_string_vector from block, where the vector loop stopped
|
||||
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
||||
|
||||
@@ -108,6 +108,9 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
// m_it is already value-initialized, so this is not needed for
|
||||
// correctness; it is kept because GCC generates faster loops with it
|
||||
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
m_it.object_iterator = typename object_t::iterator();
|
||||
|
||||
@@ -266,6 +266,13 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||
json_pointer& operator/=(const json_pointer& ptr)
|
||||
{
|
||||
// insert() must not be given iterators into the vector it inserts
|
||||
// into: append a copy when a pointer is appended to itself
|
||||
if (JSON_HEDLEY_UNLIKELY(&ptr == this))
|
||||
{
|
||||
return *this /= json_pointer(ptr);
|
||||
}
|
||||
|
||||
reference_tokens.insert(reference_tokens.end(),
|
||||
ptr.reference_tokens.begin(),
|
||||
ptr.reference_tokens.end());
|
||||
@@ -297,7 +304,7 @@ class json_pointer
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
@@ -308,7 +315,7 @@ class json_pointer
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.push_back(std::move(token));
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
@@ -2173,7 +2180,7 @@ class json_pointer
|
||||
{
|
||||
// create an entry in the array
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
break;
|
||||
break; // LCOV_EXCL_LINE (needs an array index with 10 or more digits: more than 1e9 elements)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2210,7 +2217,7 @@ class json_pointer
|
||||
json_pointer<string_t> result;
|
||||
result.reference_tokens = reference_tokens;
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
json_pointer<string_t> convert()&&
|
||||
{
|
||||
|
||||
@@ -215,6 +215,17 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Inlines small functions even in unoptimized builds, so that they are not
|
||||
// emitted. The parsers for parse(), accept(), and from_*() use it for the
|
||||
// functions that stand in for the code recovering from errors (see #3989).
|
||||
// MSVC is left to decide, as it warns (C4714) where it does not inline a
|
||||
// __forceinline function.
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE
|
||||
#else
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
@@ -347,7 +358,7 @@
|
||||
return ej_pair.second == j; \
|
||||
}); \
|
||||
if (it != std::end(m)) e = it->first; \
|
||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||
}
|
||||
|
||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_INTERNAL_ALWAYS_INLINE
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
#undef JSON_FILE_WINDOW
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||
|
||||
@@ -833,7 +833,7 @@ class binary_writer
|
||||
while (n != 0);
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||
@@ -860,7 +860,7 @@ class binary_writer
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format
|
||||
@@ -1792,7 +1792,7 @@ class binary_writer
|
||||
std::memcpy(out, &n, sizeof(NumberType));
|
||||
if (!is_little_endian)
|
||||
{
|
||||
std::reverse(out, out + sizeof(NumberType));
|
||||
std::reverse(out, out + sizeof(NumberType)); // LCOV_EXCL_LINE (big-endian hosts only)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1921,7 +1921,7 @@ class binary_writer
|
||||
const string_t& value = *j.m_data.m_value.string;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size() + 1ul)))
|
||||
{
|
||||
return false;
|
||||
return false; // LCOV_EXCL_LINE (a string of 2 GiB or more)
|
||||
}
|
||||
out[0] = to_char_type(0x02);
|
||||
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
|
||||
@@ -2088,7 +2088,8 @@ class binary_writer
|
||||
/// arrays: the first index with one more digit; only maintained by
|
||||
/// @ref calc_bson_sizes
|
||||
std::uint64_t index_boundary = 10;
|
||||
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
||||
/// write_bson_document_single_pass: the position of its size field
|
||||
std::size_t size_slot;
|
||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||
std::size_t entries_size = 0;
|
||||
@@ -2339,6 +2340,198 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SinkType>
|
||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
||||
|
||||
/// whether every integer fits the 64 bits BSON has, so that writing an
|
||||
/// array of numbers needs no range check
|
||||
static constexpr bool bson_integers_fit_64_bits =
|
||||
std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits;
|
||||
|
||||
/*!
|
||||
@brief whether BSON documents can be written in a single pass: the sink
|
||||
can overwrite what it has written (output_vector_sink), and
|
||||
@ref bson_integers_fit_64_bits
|
||||
*/
|
||||
using bson_single_pass_sink = conjunction<is_detected<sink_written_t, OutputSinkType>, std::integral_constant<bool, bson_integers_fit_64_bits>>;
|
||||
|
||||
/// write a placeholder for the size of an object or array whose entries
|
||||
/// follow; @return its position, for @ref close_bson_container
|
||||
std::size_t write_bson_size_placeholder()
|
||||
{
|
||||
const std::size_t pos = oa.written();
|
||||
// zero in any byte order
|
||||
const std::array<CharType, sizeof(std::int32_t)> zero{};
|
||||
oa.write_characters(zero.data(), zero.size());
|
||||
return pos;
|
||||
}
|
||||
|
||||
/// end the object or array whose size is at @a pos and write its size
|
||||
/// there: everything from that field to the terminating null byte
|
||||
/// @return false (and the size is not written) if it does not fit
|
||||
bool close_bson_container(const std::size_t pos)
|
||||
{
|
||||
oa.write_character(to_char_type(0x00));
|
||||
const std::size_t size = oa.written() - pos;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
const auto le_size = static_cast<std::uint32_t>(size);
|
||||
oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
||||
does, for @ref write_bson_document_single_pass
|
||||
@return at least the size of the value
|
||||
A number, boolean, or null needs no check there (its integers have at most
|
||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
||||
the same exception when it is written.
|
||||
*/
|
||||
std::size_t check_bson_value(const BasicJsonType& j)
|
||||
{
|
||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
||||
}
|
||||
|
||||
/// whether an entry of @a entry_size characters keeps the document that
|
||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
||||
/// the 32-bit size of BSON
|
||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
||||
{
|
||||
const auto limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
||||
const std::size_t written = oa.written() - start;
|
||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document in a
|
||||
single pass, writing the size of each object and array once it is
|
||||
complete
|
||||
|
||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
||||
order, so the same exception is thrown; the output written up to then is
|
||||
discarded with the vector it was written to.
|
||||
|
||||
@return false as soon as the document or a string, binary value, object,
|
||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
||||
nothing large has been written then, and the caller writes the
|
||||
document in two passes, which reports that error
|
||||
*/
|
||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
||||
{
|
||||
const std::size_t start = oa.written();
|
||||
// the object or array whose entries are being written (with the
|
||||
// position of its size), and the ones it is in
|
||||
bson_frame current(&document, write_bson_size_placeholder());
|
||||
std::vector<bson_frame> parents;
|
||||
|
||||
while (true)
|
||||
{
|
||||
const BasicJsonType* nested = nullptr;
|
||||
std::size_t nested_size_pos = 0;
|
||||
if (current.value->is_object())
|
||||
{
|
||||
const auto& object = *current.value->m_data.m_value.object;
|
||||
while (nested == nullptr && current.member != object.cend())
|
||||
{
|
||||
const auto& el = *current.member;
|
||||
++current.member;
|
||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (el.second.is_structured())
|
||||
{
|
||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
||||
nested_size_pos = write_bson_size_placeholder();
|
||||
nested = &el.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
write_bson_value(el.first, el.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& array = *current.value->m_data.m_value.array;
|
||||
while (nested == nullptr && current.index < array.size())
|
||||
{
|
||||
if (is_bson_fixed_size(array[current.index]))
|
||||
{
|
||||
write_bson_array_fixed_entries(current);
|
||||
if (current.index == array.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// a string, binary value, object or array
|
||||
const BasicJsonType& el = array[current.index];
|
||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
||||
current.next_index();
|
||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (el.is_structured())
|
||||
{
|
||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
||||
nested_size_pos = write_bson_size_placeholder();
|
||||
nested = ⪙
|
||||
}
|
||||
else
|
||||
{
|
||||
write_bson_value(index_name, el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nested != nullptr)
|
||||
{
|
||||
if (nested->is_array())
|
||||
{
|
||||
// an array of booleans, numbers and null is written without a frame on the stack
|
||||
bson_frame leaf(nested, nested_size_pos);
|
||||
write_bson_array_fixed_entries(leaf);
|
||||
if (leaf.index == nested->m_data.m_value.array->size())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
parents.push_back(std::move(current));
|
||||
current = std::move(leaf);
|
||||
continue;
|
||||
}
|
||||
parents.push_back(std::move(current));
|
||||
current = bson_frame(nested, nested_size_pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
if (parents.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = std::move(parents.back());
|
||||
parents.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document
|
||||
|
||||
@@ -2350,6 +2543,39 @@ class binary_writer
|
||||
@pre document.type() == value_t::object
|
||||
*/
|
||||
void write_bson_document(const BasicJsonType& document)
|
||||
{
|
||||
write_bson_document(document, bson_single_pass_sink());
|
||||
}
|
||||
|
||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
||||
{
|
||||
const std::size_t start = oa.written();
|
||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
||||
&& write_bson_document_single_pass(document))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// a length does not fit: start over, so that it is reported as the
|
||||
// two passes report it, before anything is written
|
||||
oa.rewind(start);
|
||||
write_bson_document_two_pass(document);
|
||||
}
|
||||
|
||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
||||
{
|
||||
write_bson_document_two_pass(document);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document: the
|
||||
sizes of all objects and arrays first, then the entries
|
||||
|
||||
@param[in] document the JSON object to serialize
|
||||
@pre document.type() == value_t::object
|
||||
*/
|
||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
||||
{
|
||||
std::vector<std::size_t> nested_sizes;
|
||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||
@@ -2552,10 +2778,10 @@ class binary_writer
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
integers are number_unsigned after parsing). The sign is bit 63 of the
|
||||
result (a narrower integer is sign-extended); computing it with masks keeps
|
||||
GCC from branching on the type. Only called if @ref integers_fit_64_bits();
|
||||
a wider number type is written with range checks on the whole value.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
@@ -2563,9 +2789,7 @@ class binary_writer
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
sign_mask = (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u));
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -2902,22 +3126,19 @@ class binary_writer
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
@@ -3080,6 +3301,25 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/// write the head and the characters of @a value, at most
|
||||
/// max_text_in_reservation of them, in one reservation
|
||||
JSON_HEDLEY_ALWAYS_INLINE void write_cbor_string_reserved(const string_t& value)
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 0x17)
|
||||
{
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0x60 + value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = to_char_type(0x78);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a CBOR text string
|
||||
|
||||
@@ -3101,19 +3341,7 @@ class binary_writer
|
||||
// the string is written as it is: head and characters in one reservation
|
||||
if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation))
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 0x17)
|
||||
{
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0x60 + value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = to_char_type(0x78);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
write_cbor_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3127,6 +3355,13 @@ class binary_writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_checked_string(const string_t& value, const BasicJsonType& context)
|
||||
{
|
||||
if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation)
|
||||
{
|
||||
check_utf8(value, context);
|
||||
write_cbor_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
|
||||
string_t storage;
|
||||
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||
|
||||
@@ -3141,6 +3376,27 @@ class binary_writer
|
||||
// MsgPack //
|
||||
/////////////
|
||||
|
||||
/// write the head and the characters of @a value, at most
|
||||
/// max_text_in_reservation of them, in one reservation
|
||||
JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_string_reserved(const string_t& value)
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 31)
|
||||
{
|
||||
// fixstr
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0xA0 | value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// str 8
|
||||
*p++ = to_char_type(0xD9);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a MessagePack str
|
||||
|
||||
@@ -3158,21 +3414,7 @@ class binary_writer
|
||||
// the string is written as it is: head and characters in one reservation
|
||||
if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation))
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 31)
|
||||
{
|
||||
// fixstr
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0xA0 | value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// str 8
|
||||
*p++ = to_char_type(0xD9);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
write_msgpack_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3185,6 +3427,13 @@ class binary_writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_checked_string(const string_t& value, const BasicJsonType& context)
|
||||
{
|
||||
if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation)
|
||||
{
|
||||
check_utf8(value, context);
|
||||
write_msgpack_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
|
||||
string_t storage;
|
||||
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||
|
||||
@@ -3805,7 +4054,7 @@ class binary_writer
|
||||
Containers, strings, high-precision numbers, booleans and null cannot be
|
||||
declared as the single type of an optimized container in BJData; such a
|
||||
container is written unoptimized. The reader rejects them with the same
|
||||
list (binary_reader::is_bjd_excluded_optimized_type()).
|
||||
list (binary_reader::is_valid_optimized_type()).
|
||||
*/
|
||||
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
||||
{
|
||||
@@ -3904,57 +4153,34 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate (dry_run) or write one BJData ND-array element of integer dtype @a T
|
||||
@return whether @a el's value is in range of @a T; always true when @a dry_run is false
|
||||
*/
|
||||
template<typename T>
|
||||
bool write_bjdata_ndarray_element(const BasicJsonType& el, const bool dry_run)
|
||||
{
|
||||
if (dry_run)
|
||||
{
|
||||
return bjdata_ndarray_value_in_range<T>(el);
|
||||
}
|
||||
using storage_type = typename std::conditional<std::is_unsigned<T>::value, std::uint64_t, std::int64_t>::type;
|
||||
write_number(static_cast<T>(el.template get<storage_type>()), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||
@brief whether the BJData ND-array element @a el can be written with dtype 'd' (single precision)
|
||||
@return whether @a el's value survives narrowing to float and back without any change
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
||||
@a dry_run is false
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN
|
||||
*/
|
||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||
static bool bjdata_ndarray_float_in_range(const BasicJsonType& el) noexcept
|
||||
{
|
||||
const auto dval = static_cast<double>(el.m_data.m_value.number_float);
|
||||
if (dry_run)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
const bool in_range = std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
return std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
return in_range;
|
||||
}
|
||||
write_number(static_cast<float>(dval), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the elements of an ND-array as the number kind @a dtype names
|
||||
(see @ref write_bjdata_ndarray), after they have been found to be
|
||||
in range by a dry run of @ref write_bjdata_ndarray_elements
|
||||
in range by @ref bjdata_ndarray_elements_in_range
|
||||
|
||||
The elements are encoded in blocks into space reserved from the sink.
|
||||
*/
|
||||
@@ -4036,20 +4262,13 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate or write every element of a BJData ND-array's `_ArrayData_`
|
||||
@brief whether every element of a BJData ND-array's `_ArrayData_` is in range for its dtype
|
||||
@param[in] array_data the `_ArrayData_` array
|
||||
@param[in] dtype the ND-array dtype marker, as returned by bjdata_ndarray_type_marker()
|
||||
@param[in] dry_run true to only range-check each element, false to write it
|
||||
@return whether every element is in range for @a dtype (always true when @a dry_run is false)
|
||||
@return whether every element is in range for @a dtype
|
||||
*/
|
||||
bool write_bjdata_ndarray_elements(const BasicJsonType& array_data, const CharType dtype, const bool dry_run)
|
||||
static bool bjdata_ndarray_elements_in_range(const BasicJsonType& array_data, const CharType dtype)
|
||||
{
|
||||
if (!dry_run)
|
||||
{
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& el : array_data)
|
||||
{
|
||||
bool ok = true;
|
||||
@@ -4057,43 +4276,39 @@ class binary_writer
|
||||
{
|
||||
case 'U':
|
||||
case 'B':
|
||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'C':
|
||||
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'i':
|
||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||
break;
|
||||
case 'u':
|
||||
ok = write_bjdata_ndarray_element<std::uint16_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||
break;
|
||||
case 'I':
|
||||
ok = write_bjdata_ndarray_element<std::int16_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||
break;
|
||||
case 'm':
|
||||
ok = write_bjdata_ndarray_element<std::uint32_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||
break;
|
||||
case 'l':
|
||||
ok = write_bjdata_ndarray_element<std::int32_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||
break;
|
||||
case 'M':
|
||||
ok = write_bjdata_ndarray_element<std::uint64_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||
break;
|
||||
case 'L':
|
||||
ok = write_bjdata_ndarray_element<std::int64_t>(el, dry_run);
|
||||
ok = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||
break;
|
||||
case 'd':
|
||||
ok = write_bjdata_ndarray_float_element(el, dry_run);
|
||||
ok = bjdata_ndarray_float_in_range(el);
|
||||
break;
|
||||
case 'D':
|
||||
default:
|
||||
// 'D' (double) already spans the full range of number_float_t
|
||||
if (!dry_run)
|
||||
{
|
||||
write_number(el.template get<double>(), true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!ok)
|
||||
@@ -4180,7 +4395,7 @@ class binary_writer
|
||||
const auto dim = el.template get<std::uint64_t>();
|
||||
if (!value_in_range_of<std::size_t>(dim))
|
||||
{
|
||||
return true;
|
||||
return true; // LCOV_EXCL_LINE (size_t is narrower than 64 bits)
|
||||
}
|
||||
const auto dim_size = static_cast<std::size_t>(dim);
|
||||
|
||||
@@ -4230,7 +4445,7 @@ class binary_writer
|
||||
// wrap (integers) or overflow to infinity (the "single" precision
|
||||
// float) instead of being reported, so such an object falls back to
|
||||
// a plain object encoding as well
|
||||
if (!write_bjdata_ndarray_elements(array_data, dtype, true))
|
||||
if (!bjdata_ndarray_elements_in_range(array_data, dtype))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -4242,7 +4457,7 @@ class binary_writer
|
||||
|
||||
write_ubjson(array_size, use_count, use_type, true, true, bjdata_version);
|
||||
|
||||
write_bjdata_ndarray_elements(array_data, dtype, false);
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4568,8 +4783,8 @@ class binary_writer
|
||||
run are encoded into space reserved from the sink, as in
|
||||
@ref write_array_elements; an integer that BON8 cannot write (an unsigned
|
||||
one above the int64 range, or one of a number type wider than 64 bits) throws
|
||||
from write_bon8_value, like any other value that is not a number. Kept out
|
||||
of line, like @ref write_cbor_array.
|
||||
out_of_range.407, as write_bon8_value does. Kept out of line, like
|
||||
@ref write_cbor_array.
|
||||
*/
|
||||
template<typename ArrayType>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
@@ -4619,10 +4834,10 @@ class binary_writer
|
||||
string_open = false;
|
||||
}
|
||||
|
||||
if (unencodable)
|
||||
if (JSON_HEDLEY_UNLIKELY(unencodable))
|
||||
{
|
||||
write_bon8_value(*it, string_open, depth + 1);
|
||||
++it;
|
||||
// an integer beyond the int64 range, as write_bon8_value would report it
|
||||
throw_integer_out_of_range(*it, input_format_t::bon8, "int64");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4657,14 +4872,13 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
{
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
// beyond 64 bits (number types wider than that)
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
}
|
||||
// beyond 64 bits (number types wider than that): throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
case value_t::number_float:
|
||||
@@ -4673,14 +4887,13 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
// too large for BON8
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
}
|
||||
// too large for BON8: throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
@@ -4794,10 +5007,9 @@ class binary_writer
|
||||
static void check_utf8(const string_t& s, const BasicJsonType& context)
|
||||
{
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
const std::size_t valid = valid_utf8_prefix(data, s.size());
|
||||
if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!valid_utf8_string(data, s.size())))
|
||||
{
|
||||
throw_invalid_utf8(data, valid, context);
|
||||
throw_invalid_utf8(data, valid_utf8_prefix(data, s.size()), context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
@@ -115,6 +116,10 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// the number of characters output_vector_sink makes writable ahead of its
|
||||
/// position at a time: 64 KiB of the vector's capacity, not all of it
|
||||
constexpr std::size_t vector_sink_window = 65536;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
@@ -128,7 +133,8 @@ constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||
/// its own write position: it grows the vector geometrically ahead of the
|
||||
/// position and copies each write to it, so a write is a capacity check and a
|
||||
/// position (making @ref vector_sink_window characters of it writable at a
|
||||
/// time) and copies each write to it, so a write is a capacity check and a
|
||||
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||
@@ -227,6 +233,31 @@ class output_vector_sink
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
/// the number of characters written so far, the position of the next one
|
||||
std::size_t written() const noexcept
|
||||
{
|
||||
return position();
|
||||
}
|
||||
|
||||
/// @brief replace the 4 bytes written before at position @a pos with
|
||||
/// those of @a n, in the byte order of the machine
|
||||
///
|
||||
/// Lets a writer put a length in front of data it has written since, such
|
||||
/// as the size of a BSON document, without computing it in advance.
|
||||
void overwrite(const std::size_t pos, const std::uint32_t n) noexcept
|
||||
{
|
||||
static_assert(sizeof(CharType) == 1, "overwrite() writes bytes");
|
||||
JSON_ASSERT(pos + sizeof(n) <= position());
|
||||
std::memcpy(v->data() + pos, &n, sizeof(n));
|
||||
}
|
||||
|
||||
/// drop the characters written from position @a pos on
|
||||
void rewind(const std::size_t pos) noexcept
|
||||
{
|
||||
JSON_ASSERT(pos <= position());
|
||||
cursor = v->data() + pos;
|
||||
}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
@@ -251,9 +282,17 @@ class output_vector_sink
|
||||
// reallocate for every few bytes
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
const std::size_t capacity = (std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2));
|
||||
v->reserve(capacity);
|
||||
// resize() zeroes the characters it adds. Zeroing all of a doubled
|
||||
// capacity, megabytes of a fresh allocation, at once faults in its
|
||||
// pages and evicts them from the cache before the writer gets there,
|
||||
// so its stores miss again; a window ahead of the position is still
|
||||
// cached when they come (the binary writers on a long array of
|
||||
// numbers: 4-26 % faster on x86, 1-2.5 % on Apple M1)
|
||||
v->resize((std::min)(capacity, (std::max)(needed, written + vector_sink_window)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // remove, fill, find, none_of, min
|
||||
#include <algorithm> // copy, none_of, min, search
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // isfinite
|
||||
@@ -1113,36 +1113,21 @@ class serializer
|
||||
{
|
||||
// the code point is at least 0x60 (smaller ones are handled
|
||||
// above) and is not printable ASCII (which the scanner
|
||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
||||
// character
|
||||
// copies). Under EnsureAscii it is DEL (0x7F): every
|
||||
// well-formed multi-byte character is escaped by
|
||||
// write_u_escapes() above, so decode() only ever accepts a
|
||||
// single byte here, and nothing of it was buffered
|
||||
if (EnsureAscii)
|
||||
{
|
||||
JSON_ASSERT(codepoint >= 0x7F);
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
// this character was buffered as raw bytes below
|
||||
// in case it turned out to be part of an
|
||||
// ill-formed sequence (which is kept as is); now
|
||||
// that it decoded to a well-formed code point,
|
||||
// undo that and \u-escape it like any other
|
||||
// character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
JSON_ASSERT(codepoint == 0x7F);
|
||||
JSON_ASSERT(bytes == bytes_after_last_accept);
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes of the
|
||||
// sequence have been copied in the default case below)
|
||||
string_buffer[bytes++] = s[i];
|
||||
string_buffer[bytes++] = s[i]; // LCOV_EXCL_LINE (well-formed sequences are copied in runs)
|
||||
}
|
||||
|
||||
reserve_string_buffer(bytes);
|
||||
@@ -1977,6 +1962,23 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
// 17 to 20 digits: the first one to four, then twice eight
|
||||
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
|
||||
{
|
||||
const auto value = static_cast<std::uint64_t>(abs_value);
|
||||
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
|
||||
const std::uint64_t low16 = value - (top * 10000000000000000u);
|
||||
const std::uint64_t middle = low16 / 100000000u;
|
||||
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
|
||||
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
||||
char* const p = buffer_ptr + n_chars;
|
||||
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
|
||||
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
|
||||
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
|
||||
write_buffer_pos += n_chars + 24 - leading;
|
||||
return;
|
||||
}
|
||||
|
||||
n_chars += count_digits(abs_value);
|
||||
|
||||
// spare 1 byte for '\0'
|
||||
@@ -2078,31 +2080,35 @@ class serializer
|
||||
// check if the buffer was large enough
|
||||
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// look up the locale's thousands separator and decimal point now,
|
||||
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
||||
// look up the locale's decimal point now, matching what
|
||||
// snprintf_float() just used (see get_decimal_point() in
|
||||
// number_parse.hpp); it is a string, which can be longer than one
|
||||
// byte (U+066B in ps_AF, #5993).
|
||||
// A thousands separator needs no handling: %g never groups digits.
|
||||
const auto* loc = std::localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
||||
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
||||
|
||||
// erase thousands separators
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
const char* const decimal_point = (loc->decimal_point == nullptr) ? "" : loc->decimal_point;
|
||||
const std::size_t decimal_point_len = std::char_traits<char>::length(decimal_point);
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (decimal_point != '\0' && decimal_point != '.')
|
||||
if ((decimal_point_len > 1) || (decimal_point_len == 1 && decimal_point[0] != '.'))
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
const auto first = number_buffer.begin();
|
||||
const auto last = first + len;
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::search returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::search(first, last, decimal_point, decimal_point + decimal_point_len);
|
||||
if (dec_pos != last)
|
||||
{
|
||||
*dec_pos = '.';
|
||||
if (decimal_point_len > 1)
|
||||
{
|
||||
// move the fraction and exponent behind the '.' (std::copy
|
||||
// allows the overlap, as the destination is before the source)
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::copy returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::copy(dec_pos + static_cast<std::ptrdiff_t>(decimal_point_len), last, dec_pos + 1);
|
||||
*end = '\0';
|
||||
len = end - first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2122,6 +2128,23 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// whether every number_magnitude_t fits 64 bits
|
||||
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
|
||||
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
|
||||
|
||||
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
|
||||
/// 64 bits (selected by type, as the comparison would be reported as
|
||||
/// always true), else compared
|
||||
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
|
||||
{
|
||||
return x <= 18446744073709551615u;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for dump_integer
|
||||
*
|
||||
|
||||
@@ -139,7 +139,7 @@ inline OutStringType concat(Args && ... args)
|
||||
str.reserve(concat_length(args...));
|
||||
concat_into(str, std::forward<Args>(args)...);
|
||||
return str;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -83,10 +83,6 @@ template<typename StringType>
|
||||
inline void unescape(StringType& s)
|
||||
{
|
||||
auto pos = s.find_first_of('~', 0);
|
||||
if (pos == StringType::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size());
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -36,7 +37,7 @@ StringType to_string(std::size_t value)
|
||||
StringType result;
|
||||
int_to_string(result, value);
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
inline std::string hex_byte(const std::uint8_t byte)
|
||||
@@ -46,7 +47,7 @@ inline std::string hex_byte(const std::uint8_t byte)
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
/*!
|
||||
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||
@@ -390,7 +391,7 @@ inline StringType sanitize_utf8(const StringType& s, const error_handler_t error
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -69,7 +69,8 @@ struct parse_failure
|
||||
};
|
||||
|
||||
/// FloatType: the number_float_t of the document, whose overflow parse() rejects
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
|
||||
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
|
||||
class builder
|
||||
{
|
||||
public:
|
||||
@@ -173,31 +174,16 @@ class builder
|
||||
}
|
||||
if (p[1] == '/')
|
||||
{
|
||||
p += 2;
|
||||
// (as in parse(), a null byte is the end of the input, so it is
|
||||
// left for the caller to see)
|
||||
while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
return skip_line_comment<NulIsEnd>(p + 2, m_end);
|
||||
}
|
||||
if (p[1] == '*')
|
||||
{
|
||||
p += 2;
|
||||
for (;;)
|
||||
p = find_block_comment_end<NulIsEnd>(p + 2, m_end);
|
||||
if (p == m_end || *p != '*')
|
||||
{
|
||||
if (p == m_end || (NulIsEnd && *p == 0))
|
||||
{
|
||||
return fail_at(error_code::comment_unterminated, p);
|
||||
}
|
||||
if (*p == '*' && p + 1 != m_end && p[1] == '/')
|
||||
{
|
||||
p += 2;
|
||||
return p;
|
||||
}
|
||||
++p;
|
||||
return fail_at(error_code::comment_unterminated, p);
|
||||
}
|
||||
return p + 2;
|
||||
}
|
||||
++p;
|
||||
return fail_at(error_code::comment_start, p);
|
||||
@@ -260,11 +246,87 @@ class builder
|
||||
return p;
|
||||
}
|
||||
|
||||
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
|
||||
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
|
||||
/// slow_string(), for everything up to the closing quote
|
||||
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
|
||||
{
|
||||
const unsigned char* const end = m_end;
|
||||
for (;;)
|
||||
{
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
const unsigned char c = *p;
|
||||
if (c == '"')
|
||||
{
|
||||
++p;
|
||||
return decoded{p, 0, 0};
|
||||
}
|
||||
if (c != '\\')
|
||||
{
|
||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||
}
|
||||
++p;
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
const unsigned char d = *p++;
|
||||
if (d == 'u')
|
||||
{
|
||||
const int cp = hex4(p); // (moves p past the four digits)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
|
||||
{
|
||||
return failed(error_code::string_unicode_hex, p);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
|
||||
{
|
||||
if (cp >= 0xDC00)
|
||||
{
|
||||
return failed(error_code::string_surrogate_low, p);
|
||||
}
|
||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
p += 2;
|
||||
const int lo = hex4(p); // (moves p past the four digits)
|
||||
if (lo < 0)
|
||||
{
|
||||
return failed(error_code::string_unicode_hex, p);
|
||||
}
|
||||
if (lo < 0xDC00 || lo > 0xDFFF)
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
|
||||
{
|
||||
--p;
|
||||
return failed(error_code::string_escape, p);
|
||||
}
|
||||
if (p != end && *p == '\\')
|
||||
{
|
||||
continue; // (escapes in a row: no run of text between them)
|
||||
}
|
||||
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
|
||||
}
|
||||
}
|
||||
|
||||
/// s points to the start of the string, p to a backslash, a control
|
||||
/// character, an ill-formed UTF-8 byte, or the end: decode the string into
|
||||
/// the arena
|
||||
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
|
||||
{
|
||||
if (!enabled(Store))
|
||||
{
|
||||
// (accept(): the string is checked, not decoded)
|
||||
static_cast<void>(s);
|
||||
return check_string(p);
|
||||
}
|
||||
// single-character escapes; 0: invalid (and 'u', handled separately)
|
||||
static const std::array<char, 128> simple_escape =
|
||||
{
|
||||
@@ -358,6 +420,21 @@ class builder
|
||||
}
|
||||
arena_ensure(w, w_end, 4);
|
||||
w = put_utf8(w, cp);
|
||||
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
|
||||
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
|
||||
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
|
||||
// the same error at the same position as it would without this loop.
|
||||
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
|
||||
{
|
||||
const int next = hex_codepoint(p + 2);
|
||||
if (next < 0 || (next & 0xF800) == 0xD800)
|
||||
{
|
||||
break;
|
||||
}
|
||||
p += 6;
|
||||
arena_ensure(w, w_end, 4);
|
||||
w = put_utf8(w, next);
|
||||
}
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||
{
|
||||
@@ -625,10 +702,13 @@ run_found:
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
|
||||
{
|
||||
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
|
||||
base = cold.m_doc.nodes;
|
||||
out = base;
|
||||
cap = base + cold.m_doc.node_capacity;
|
||||
if (enabled(Store))
|
||||
{
|
||||
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
|
||||
base = cold.m_doc.nodes;
|
||||
out = base;
|
||||
cap = base + cold.m_doc.node_capacity;
|
||||
}
|
||||
|
||||
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
|
||||
{
|
||||
@@ -812,7 +892,10 @@ close_object:
|
||||
// an array pays nothing for this)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
|
||||
{
|
||||
cold.note_large_object(cur_idx);
|
||||
if (enabled(Store))
|
||||
{
|
||||
cold.note_large_object(cur_idx);
|
||||
}
|
||||
}
|
||||
|
||||
close_container:
|
||||
@@ -972,6 +1055,12 @@ indent_done:
|
||||
/// an integer's value); two stores on little-endian targets
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
|
||||
{
|
||||
if (!enabled(Store))
|
||||
{
|
||||
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
|
||||
// null: their difference, which open() takes, is 0.
|
||||
return out;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(out - base);
|
||||
@@ -1030,9 +1119,12 @@ indent_done:
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void close()
|
||||
{
|
||||
node& n = base[cur_idx];
|
||||
n.len = cur_count;
|
||||
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
|
||||
if (enabled(Store))
|
||||
{
|
||||
node& n = base[cur_idx];
|
||||
n.len = cur_count;
|
||||
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
|
||||
}
|
||||
if (--depth != 0)
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
|
||||
@@ -1076,19 +1168,20 @@ indent_done:
|
||||
if (negative)
|
||||
{
|
||||
++p;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cur() < '0' || cur() > '9'))
|
||||
{
|
||||
return fail(error_code::number_after_minus);
|
||||
}
|
||||
}
|
||||
const unsigned char* const int_start = p;
|
||||
if (cur() == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
{
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
return fail(error_code::number_after_minus);
|
||||
// (a digit: the dispatch or the check after the sign saw it)
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||
std::size_t frac_digits = 0;
|
||||
@@ -1097,7 +1190,7 @@ indent_done:
|
||||
{
|
||||
++p;
|
||||
const unsigned char* const f0 = p;
|
||||
p = skip_digits(p, e);
|
||||
p = skip_fraction_digits(p, e);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(p == f0))
|
||||
{
|
||||
return fail(error_code::number_after_dot);
|
||||
@@ -1193,41 +1286,58 @@ indent_done:
|
||||
return false;
|
||||
}
|
||||
p = r.p;
|
||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// the decoded string is in the arena: next keeps the source offset
|
||||
// of the opening quote plus one (0: unknown) for materialize()
|
||||
const std::uint64_t second = static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32);
|
||||
#else
|
||||
const std::uint64_t second = r.len;
|
||||
#endif
|
||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, second);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// run the builder with compile-time options
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store>
|
||||
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
|
||||
{
|
||||
if (sentinel)
|
||||
{
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size);
|
||||
const bool ok = bld.run();
|
||||
failure = bld.failure();
|
||||
return ok;
|
||||
}
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size);
|
||||
const bool ok = bld.run();
|
||||
failure = bld.failure();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
|
||||
/// number_float_t of the document
|
||||
/// number_float_t of the document; validate_only: accept(), only the result is
|
||||
/// used
|
||||
template<typename FloatType, bool NulIsEnd>
|
||||
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
|
||||
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only,
|
||||
parse_failure& failure)
|
||||
{
|
||||
if (validate_only && !comments && !trailing_commas)
|
||||
{
|
||||
// accept() of plain JSON: nothing is stored. The other option sets
|
||||
// could skip the stores just as well; they keep the builder that
|
||||
// stores only so that there are two more instantiations of the parse
|
||||
// loop, not eight.
|
||||
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
|
||||
}
|
||||
if (comments)
|
||||
{
|
||||
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
|
||||
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure);
|
||||
}
|
||||
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
|
||||
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure);
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -26,6 +26,7 @@
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
#include <nlohmann/detail/view/view_mode.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -46,10 +47,9 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
||||
/// (type_error.316 with the same message), so that an editable document
|
||||
/// only holds valid UTF-8: the error is at the first byte that no
|
||||
/// well-formed sequence can continue with (Unicode, Table 3-7).
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void check_utf8_bytewise(const unsigned char* p, std::size_t n, std::size_t i)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
for (std::size_t i = 0; i < n;)
|
||||
while (i < n)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
if (c < 0x80)
|
||||
@@ -79,6 +79,43 @@ inline void check_utf8(const char* s, std::size_t n)
|
||||
}
|
||||
}
|
||||
|
||||
/// The long-string path of check_utf8(): skips well-formed runs with the
|
||||
/// parser's string scan (which stops at a quote, a backslash, or a control
|
||||
/// character, all legal here, and at ill-formed UTF-8); the byte-wise check
|
||||
/// runs only from where the scan found ill-formed UTF-8, to report it.
|
||||
NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::size_t n)
|
||||
{
|
||||
const unsigned char* const e = p + n;
|
||||
const unsigned char* q = p;
|
||||
for (;;)
|
||||
{
|
||||
q = scan_string_run<true>(q, e);
|
||||
if (q == e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (*q >= 0x80)
|
||||
{
|
||||
break; // ill-formed UTF-8: reported below
|
||||
}
|
||||
++q; // a quote, backslash, or control character
|
||||
}
|
||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||
}
|
||||
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n >= 32))
|
||||
{
|
||||
check_utf8_scan(p, n);
|
||||
return;
|
||||
}
|
||||
// short strings: the vector scan costs more than it saves; (the loop is
|
||||
// inlined with a constant start, so this path is the plain byte-wise check)
|
||||
check_utf8_bytewise(p, n, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the edits of an editable basic_json_document
|
||||
|
||||
@@ -120,7 +157,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != node_kind::object && o->kind != node_kind::null)
|
||||
{
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -171,9 +208,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
check_index(a, idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||
assign(slot, e, a, true);
|
||||
@@ -188,7 +225,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array && a->kind != node_kind::null)
|
||||
{
|
||||
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == node_kind::null)
|
||||
@@ -212,9 +249,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len + 1);
|
||||
check_index(a, idx, a->len + 1);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, a, 1);
|
||||
@@ -232,7 +269,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != node_kind::object)
|
||||
{
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
}
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
@@ -250,9 +287,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
check_index(a, idx, a->len);
|
||||
node* const h = block_of(m_doc, a, 0);
|
||||
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
||||
--h->next;
|
||||
@@ -279,11 +316,11 @@ class editor
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
}
|
||||
|
||||
static void check_index(std::size_t idx, std::size_t limit)
|
||||
void check_index(const node* array, std::size_t idx, std::size_t limit) const
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_array_index_out_of_range(idx);
|
||||
throw_array_index_out_of_range(error_site(&m_doc, array), idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
||||
e.moved_cap[n->off] = cap;
|
||||
return;
|
||||
}
|
||||
reserve_moved(d);
|
||||
// every caller has called reserve_moved() (see above), so neither push_back() can throw
|
||||
e.moved.push_back(seq);
|
||||
e.moved_cap.push_back(cap);
|
||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||
@@ -243,7 +243,7 @@ inline node* find_parent(const document_data& d, const node* target)
|
||||
}
|
||||
if (target == lo)
|
||||
{
|
||||
return nullptr;
|
||||
return nullptr; // LCOV_EXCL_LINE (the root, or a new value's root, which is linked)
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <cstddef> // size_t
|
||||
#include <string> // string
|
||||
#include <string> // string, to_string
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/builder.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/materialize.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -22,8 +26,292 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the value an exception is about
|
||||
|
||||
basic_json passes the value to the exception, which then names its path
|
||||
(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A
|
||||
view's value has no parent pointer and no stored positions: both are found
|
||||
from the document and the node, when the exception is thrown (so only the
|
||||
error path pays for them). Without the two macros, a site is empty.
|
||||
*/
|
||||
class error_site
|
||||
{
|
||||
public:
|
||||
error_site() noexcept = default;
|
||||
|
||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||
error_site(const document_data* d, const node* n) noexcept
|
||||
: m_doc(d), m_node(n)
|
||||
{}
|
||||
|
||||
const document_data* doc() const noexcept
|
||||
{
|
||||
return m_doc;
|
||||
}
|
||||
const node* value() const noexcept
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
|
||||
private:
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
#else
|
||||
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
|
||||
#endif
|
||||
};
|
||||
|
||||
/// the site of a view's value (the views make this a friend)
|
||||
struct view_access
|
||||
{
|
||||
template<typename View>
|
||||
static error_site site(const View& v) noexcept
|
||||
{
|
||||
return {v.m_doc, v.m_node};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename View>
|
||||
error_site site_of(const View& v) noexcept
|
||||
{
|
||||
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
|
||||
return view_access::site(v);
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
/*!
|
||||
@brief the JSON pointer tokens from the root of document @a d to node @a target
|
||||
|
||||
Nodes have no parent index, so the path is found by descending from the root:
|
||||
before the first edit, only into the subtree that contains the target (a
|
||||
container spans the next `next` nodes); after edits, elements can live in
|
||||
other sequences, so every container is searched. Iterative (the nesting depth
|
||||
is limited by memory only). False if the target is not found (then tokens is
|
||||
unspecified).
|
||||
*/
|
||||
template<bool Editable>
|
||||
bool value_path(const document_data& d, const node* target, std::vector<std::string>& tokens)
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
struct frame
|
||||
{
|
||||
const node* pos;
|
||||
const node* end;
|
||||
std::size_t index;
|
||||
bool object;
|
||||
};
|
||||
const node* const root = d.nodes;
|
||||
if (target == root)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!is_container(*root))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::vector<frame> stack;
|
||||
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
||||
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
||||
for (;;)
|
||||
{
|
||||
// a copy: pop_back() and push_back() below change the stack
|
||||
const frame f = stack.back();
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
tokens.pop_back();
|
||||
continue;
|
||||
}
|
||||
const node* entry = f.pos;
|
||||
std::string token;
|
||||
if (f.object)
|
||||
{
|
||||
// (as basic_json: the key up to a null byte)
|
||||
token.assign(d.str(*entry), entry->len);
|
||||
const std::size_t nul = token.find('\0');
|
||||
if (nul != std::string::npos)
|
||||
{
|
||||
token.resize(nul);
|
||||
}
|
||||
++entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
token = std::to_string(f.index);
|
||||
}
|
||||
const node* const child = nav::value(entry);
|
||||
if (child == target)
|
||||
{
|
||||
tokens.push_back(token);
|
||||
return true;
|
||||
}
|
||||
stack.back().pos = document_data::after(entry);
|
||||
++stack.back().index;
|
||||
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/*!
|
||||
@brief the bytes [start, end) of node @a n in the source, as materialize() sets them
|
||||
|
||||
Scalars as position_source::set_scalar; an array or object from its opening
|
||||
bracket to its closing bracket, which is found by following the chain of last
|
||||
elements to a scalar (or an empty container) and scanning from there to the
|
||||
closing brackets. None (npos) for values written by edits, and for containers
|
||||
whose last element has none, as with materialize().
|
||||
*/
|
||||
template<bool Editable>
|
||||
void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end)
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
position_source p;
|
||||
start = std::string::npos;
|
||||
end = std::string::npos;
|
||||
if (!is_container(*n))
|
||||
{
|
||||
p.set_scalar(d, *n);
|
||||
start = p.start;
|
||||
end = p.end;
|
||||
return;
|
||||
}
|
||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
||||
std::size_t closing = 0;
|
||||
std::size_t pos = std::string::npos;
|
||||
for (const node* c = n;;)
|
||||
{
|
||||
if (!is_container(*c))
|
||||
{
|
||||
p.set_scalar(d, *c);
|
||||
pos = p.end;
|
||||
break;
|
||||
}
|
||||
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
++closing;
|
||||
const node* const first = nav::first(d, c);
|
||||
const node* const last_end = nav::end(d, c);
|
||||
if (first == last_end)
|
||||
{
|
||||
pos = std::size_t{c->off} + 1;
|
||||
break;
|
||||
}
|
||||
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
||||
const node* last = first;
|
||||
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
||||
{
|
||||
last = e;
|
||||
}
|
||||
c = nav::value(last + value_offset);
|
||||
}
|
||||
for (; closing != 0 && pos != std::string::npos; --closing)
|
||||
{
|
||||
pos = position_source::container_end(d.base[0], d.size, pos);
|
||||
}
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
start = n->off;
|
||||
end = pos;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the context basic_json puts in front of the message of an exception about
|
||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root)
|
||||
///
|
||||
/// This and the throw helpers that take an error_site are templates, so that
|
||||
/// the path and range code is instantiated only where an exception can be
|
||||
/// thrown: instantiated in every translation unit (as a non-template inline
|
||||
/// function makes it), it changed GCC's early inlining of unrelated view
|
||||
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
||||
/// into itself (2.7 times the instructions).
|
||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||
template<typename Unused = void>
|
||||
std::string diagnostics(const error_site site)
|
||||
{
|
||||
if (site.doc() == nullptr || site.value() == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
const document_data& d = *site.doc();
|
||||
const bool edited = d.edits != nullptr;
|
||||
std::string context;
|
||||
#if JSON_DIAGNOSTICS
|
||||
std::vector<std::string> tokens;
|
||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
||||
if (!found)
|
||||
{
|
||||
// a value that is not found has no path, and with it no bytes
|
||||
return context;
|
||||
}
|
||||
if (!tokens.empty())
|
||||
{
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string path;
|
||||
for (const std::string& token : tokens)
|
||||
{
|
||||
path.push_back('/');
|
||||
path += detail::escape(token);
|
||||
}
|
||||
context = concat('(', path, ") ");
|
||||
}
|
||||
#endif
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::size_t start = 0;
|
||||
std::size_t end = 0;
|
||||
if (edited)
|
||||
{
|
||||
value_range<true>(d, site.value(), start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
value_range<false>(d, site.value(), start, end);
|
||||
}
|
||||
if (start != std::string::npos && end != std::string::npos)
|
||||
{
|
||||
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
||||
}
|
||||
#endif
|
||||
return context;
|
||||
}
|
||||
#else
|
||||
template<typename Unused = void>
|
||||
std::string diagnostics(const error_site /*site*/)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the context of an exception about view @a v (see exception_prefix())
|
||||
template<typename View>
|
||||
std::string diagnostics_of(const View& v)
|
||||
{
|
||||
return diagnostics<>(site_of(v));
|
||||
}
|
||||
|
||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||
// small enough to be inlined.
|
||||
// small enough to be inlined. The overloads with an error_site put its context
|
||||
// in front of the message.
|
||||
//
|
||||
// The overloads without one are for exceptions that are not about a value of a
|
||||
// document: limits and invalid input of the editing and image functions, a
|
||||
// discarded view (it has no node), a view of another document (its path would
|
||||
// be one in that document), and those that basic_json creates without a
|
||||
// context too (the errors of json_pointer, invalid UTF-8). The one exception
|
||||
// about a value that has no site is key(), see view_iterator::key().
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
@@ -45,6 +333,24 @@ namespace view
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
@@ -53,22 +359,23 @@ namespace view
|
||||
|
||||
/// out_of_range.401: the array index @a idx is not below the size
|
||||
template<typename Index>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx)
|
||||
{
|
||||
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size)
|
||||
{
|
||||
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.403: the object has no member @a key
|
||||
template<typename Key>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key)
|
||||
{
|
||||
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
}
|
||||
|
||||
/// SAX handler of the error replay: accepts every event without storing it,
|
||||
@@ -112,10 +419,12 @@ struct throwing_sax
|
||||
return true;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START (throw_parse_failure parses JSON only)
|
||||
bool binary(binary_t& /*unused*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
bool start_object(std::size_t /*unused*/)
|
||||
{
|
||||
@@ -144,11 +453,21 @@ struct throwing_sax
|
||||
|
||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||
/// exception type, which is thrown as is)
|
||||
///
|
||||
/// It is not [[noreturn]]: sax_parse() recovers from an error if
|
||||
/// parse_error() returns true, and MSVC reports that code as unreachable
|
||||
/// (C4702) for a handler that never returns. The flag hides that from it.
|
||||
template<class Exception>
|
||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
if (throwing)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(ex);
|
||||
}
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
bool throwing = true;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -80,7 +80,8 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
/// Copy the current structure of an edited document into nodes in document
|
||||
/// order, as the parser would have written them. Text written by edits is
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them.
|
||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
||||
/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS).
|
||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||
{
|
||||
struct frame
|
||||
@@ -103,16 +104,18 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
||||
return r;
|
||||
};
|
||||
const auto copy_node = [&](const node * v)
|
||||
{
|
||||
node r = *v;
|
||||
const auto is_new = static_cast<std::uint8_t>(v->flags & node_flags::is_new);
|
||||
switch (static_cast<value_t>(v->kind))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
@@ -129,7 +132,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
@@ -139,22 +142,23 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
{
|
||||
r = node{}; // nan and infinity: null, as dump() writes them
|
||||
r.kind = node_kind::null;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = 0;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
@@ -254,6 +258,81 @@ struct number_token
|
||||
bool is_float; ///< a fraction or an exponent
|
||||
};
|
||||
|
||||
/// advance over ASCII digits, eight bytes at a time
|
||||
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
while (e - p >= 8)
|
||||
{
|
||||
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
|
||||
// out of a byte with bit 7 set only affects the bytes above, after the first one
|
||||
// that count_trailing_zeros finds.)
|
||||
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
|
||||
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
|
||||
if (not_digit != 0)
|
||||
{
|
||||
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// whether the n bytes at p are ASCII digits (n > 0)
|
||||
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
|
||||
{
|
||||
const auto digits = [](const unsigned char* q)
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
|
||||
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
|
||||
};
|
||||
if (n >= 8)
|
||||
{
|
||||
for (std::size_t i = 0; i + 8 < n; i += 8)
|
||||
{
|
||||
if (!digits(p + i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (!is_digit(p[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Whether a float token has the shape that the parser records for most of
|
||||
/// them: [-]digits.digits, with the numbers of digits of the node's layout
|
||||
/// (extra). Then the token is a number of that layout, and the general scan
|
||||
/// that follows is not needed.
|
||||
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
|
||||
{
|
||||
const std::size_t int_digits = extra & 0xFFu;
|
||||
const std::size_t frac_digits = extra >> 8u;
|
||||
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t negative = *s == '-' ? 1 : 0;
|
||||
if (len != negative + int_digits + 1 + frac_digits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const p = s + negative;
|
||||
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
|
||||
}
|
||||
|
||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||
{
|
||||
@@ -272,11 +351,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
p = digit_run(p + 1, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -288,10 +363,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
const unsigned char* const f0 = ++p;
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
p = digit_run(p, e);
|
||||
if (p == f0)
|
||||
{
|
||||
return false;
|
||||
@@ -402,10 +474,15 @@ struct float_range
|
||||
template<typename Check>
|
||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||
// (save() writes the ranges in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const byte_range & a, const byte_range & b)
|
||||
{
|
||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||
});
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
std::size_t end = 0;
|
||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||
{
|
||||
@@ -427,7 +504,8 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
/// record layouts that match it), tokens must not overlap.
|
||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||
// (save() writes the tokens in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const float_range & a, const float_range & b)
|
||||
{
|
||||
if (a.off != b.off)
|
||||
{
|
||||
@@ -438,7 +516,11 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
return a.len < b.len;
|
||||
}
|
||||
return a.extra < b.extra;
|
||||
});
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
std::size_t end = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < ranges.size())
|
||||
@@ -448,14 +530,18 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
{
|
||||
return false;
|
||||
}
|
||||
number_token t{};
|
||||
const unsigned char* const s = text + r.off;
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
std::uint16_t layout = r.extra;
|
||||
if (!plain_float_token(s, r.len, r.extra))
|
||||
{
|
||||
return false;
|
||||
number_token t{};
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
layout = float_layout(t);
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
const std::uint16_t layout = float_layout(t);
|
||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||
{
|
||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||
@@ -515,13 +601,15 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
std::vector<float_range> floats;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
// (next: the source offset of a decoded string's opening quote plus
|
||||
// one, or 0; see node)
|
||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -593,13 +681,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -613,7 +701,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if (n.flags != 0 || !number_in_bounds(n))
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -635,7 +723,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -699,10 +787,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
||||
d.edits.reset();
|
||||
d.base[2] = nullptr;
|
||||
d.owned.clear();
|
||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
||||
{
|
||||
d.owned_image.clear();
|
||||
}
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
|
||||
@@ -105,7 +105,10 @@ class view_iterator
|
||||
}
|
||||
|
||||
/// the key of the current object member; throws invalid_iterator.207 for
|
||||
/// other iterators, like basic_json's iterators
|
||||
/// other iterators, like basic_json's iterators (but without the context
|
||||
/// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not
|
||||
/// know the value it iterates over, and keeping it would make iterators
|
||||
/// larger, which keeps GCC from inlining recursive walks)
|
||||
string_view_t key() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||
|
||||
@@ -40,6 +40,15 @@
|
||||
#define NLOHMANN_VIEW_NOINLINE
|
||||
#endif
|
||||
|
||||
// GCC: a function compiled as written, without the clones in which interprocedural
|
||||
// constant propagation drops an argument that every caller passes as the same constant
|
||||
// (calling such a clone changes the code GCC generates for the parse loop that calls it)
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#define NLOHMANN_VIEW_NOCLONE __attribute__((noclone))
|
||||
#else
|
||||
#define NLOHMANN_VIEW_NOCLONE
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define NLOHMANN_VIEW_NODISCARD __attribute__((warn_unused_result))
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#undef NLOHMANN_VIEW_UNLIKELY
|
||||
#undef NLOHMANN_VIEW_ALWAYS_INLINE
|
||||
#undef NLOHMANN_VIEW_NOINLINE
|
||||
#undef NLOHMANN_VIEW_NOCLONE
|
||||
#undef NLOHMANN_VIEW_NODISCARD
|
||||
#undef NLOHMANN_VIEW_THROW
|
||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/number.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -26,6 +27,184 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/*!
|
||||
@brief the source positions of the values materialize() replays
|
||||
|
||||
Stands in for the lexer of json_sax_dom_parser, which reads the positions of
|
||||
the values it stores from these three functions (diagnostic_positions). npos
|
||||
is a value without a source position. One position_source serves a whole
|
||||
materialize() call: before each value is handed to the handler, the range of
|
||||
that value is set (or cleared), overwriting that of the value before.
|
||||
*/
|
||||
struct position_source
|
||||
{
|
||||
/// the token, of which diagnostic_positions only reads the length
|
||||
struct token
|
||||
{
|
||||
std::size_t length;
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t start = std::string::npos; ///< the first byte of the next value
|
||||
std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket)
|
||||
|
||||
std::size_t get_chars_read_total() const noexcept
|
||||
{
|
||||
return end;
|
||||
}
|
||||
std::size_t get_token_start_position() const noexcept
|
||||
{
|
||||
return start;
|
||||
}
|
||||
token get_string() const noexcept
|
||||
{
|
||||
return token{end - start};
|
||||
}
|
||||
|
||||
/// the range [first, last)
|
||||
void set(std::size_t first, std::size_t last) noexcept
|
||||
{
|
||||
start = first;
|
||||
end = last;
|
||||
}
|
||||
|
||||
/// no range: the next value has no source position
|
||||
void clear() noexcept
|
||||
{
|
||||
start = std::string::npos;
|
||||
end = std::string::npos;
|
||||
}
|
||||
|
||||
/// one past the closing quote of the string whose opening quote is at
|
||||
/// src[quote], or npos (the scan is bounded by size, as images can be
|
||||
/// loaded without a check)
|
||||
static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept
|
||||
{
|
||||
for (std::size_t i = quote + 1; i < size; ++i)
|
||||
{
|
||||
if (src[i] == '\\')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else if (src[i] == '"')
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
/// one past the closing bracket of the array or object whose last element
|
||||
/// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos;
|
||||
/// in between, there can only be whitespace, comments, and a trailing comma
|
||||
/// (the source was validated when it was parsed)
|
||||
static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept
|
||||
{
|
||||
const auto* const b = reinterpret_cast<const unsigned char*>(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it
|
||||
const unsigned char* const e = b + size;
|
||||
while (pos < size)
|
||||
{
|
||||
switch (src[pos])
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case ',':
|
||||
++pos;
|
||||
break;
|
||||
case ']':
|
||||
case '}':
|
||||
return pos + 1;
|
||||
case '/':
|
||||
if (pos + 1 < size && src[pos + 1] == '/')
|
||||
{
|
||||
pos = static_cast<std::size_t>(skip_line_comment(b + pos + 2, e) - b);
|
||||
break;
|
||||
}
|
||||
if (pos + 1 < size && src[pos + 1] == '*')
|
||||
{
|
||||
// (unterminated: pos is size, which ends the loop)
|
||||
pos = static_cast<std::size_t>(find_block_comment_end(b + pos + 2, e) - b) + 2;
|
||||
break;
|
||||
}
|
||||
return std::string::npos;
|
||||
default:
|
||||
return std::string::npos;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
/// the range of the scalar n of document d
|
||||
void set_scalar(const document_data& d, const node& n) noexcept
|
||||
{
|
||||
if ((n.flags & node_flags::is_new) != 0)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
std::size_t first = std::string::npos;
|
||||
std::size_t last = std::string::npos;
|
||||
switch (n.kind)
|
||||
{
|
||||
case node_kind::string:
|
||||
if ((n.flags & node_flags::storage) == 0)
|
||||
{
|
||||
// off is the first byte after the opening quote
|
||||
if (n.off != 0)
|
||||
{
|
||||
first = n.off - 1;
|
||||
last = n.off + std::size_t{n.len} + 1;
|
||||
}
|
||||
}
|
||||
else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0)
|
||||
{
|
||||
// next is the offset of the opening quote plus one
|
||||
first = n.next - 1;
|
||||
last = string_end(d.base[0], d.size, first);
|
||||
}
|
||||
break;
|
||||
case node_kind::number_integer:
|
||||
case node_kind::number_unsigned:
|
||||
case node_kind::number_float:
|
||||
if ((n.flags & node_flags::storage) == 0)
|
||||
{
|
||||
first = n.off;
|
||||
last = n.off + number_length(n);
|
||||
}
|
||||
break;
|
||||
case node_kind::boolean:
|
||||
first = n.off;
|
||||
last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
||||
break;
|
||||
case node_kind::null:
|
||||
first = n.off;
|
||||
last = n.off + 4u;
|
||||
break;
|
||||
case node_kind::object: // LCOV_EXCL_LINE
|
||||
case node_kind::array:
|
||||
case node_kind::link:
|
||||
default:
|
||||
break; // LCOV_EXCL_LINE
|
||||
}
|
||||
// (a crafted image can point past the text)
|
||||
if (last == std::string::npos || last > d.size)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
set(first, last);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief the basic_json value of the subtree at n
|
||||
|
||||
@@ -33,14 +212,24 @@ The subtree is replayed into the SAX handler that parse() uses to build its
|
||||
values, so the result is the value parse() would produce: duplicate keys keep
|
||||
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
||||
iterative, so the nesting depth is limited by memory only, as for parse().
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
|
||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
||||
parse(): position_source stands in for the lexer. Values written by edits have
|
||||
none (npos), and neither has an array or object whose elements changed, or
|
||||
whose last element has none (the end of its closing bracket is found by
|
||||
scanning from the end of the last element).
|
||||
*/
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||
using input_t = iterator_input_adapter<const char*>;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
using lexer_t = position_source;
|
||||
#else
|
||||
using lexer_t = lexer<BasicJsonType, input_t>; // (the default: unused, as no lexer is passed)
|
||||
#endif
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, input_t, true, lexer_t>;
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
@@ -52,9 +241,15 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
const node* pos;
|
||||
const node* end;
|
||||
bool object;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::size_t start; ///< the opening bracket (npos: unknown)
|
||||
std::size_t last_end; ///< the end of the last element read (npos: unknown)
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||
// a scalar needs neither the handler nor the stack of open containers
|
||||
// (with positions, it goes through the handler, which sets them)
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
@@ -76,10 +271,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
BasicJsonType result;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
position_source positions;
|
||||
sax_t sax(result, true, &positions);
|
||||
#else
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
#endif
|
||||
const string_t no_token {};
|
||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||
std::vector<frame> deep{};
|
||||
@@ -94,6 +295,16 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
std::size_t depth = 0;
|
||||
for (;;)
|
||||
{
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
if (!is_container(*n))
|
||||
{
|
||||
positions.set_scalar(d, *n);
|
||||
if (depth != 0)
|
||||
{
|
||||
open[depth - 1].last_end = positions.end;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
@@ -102,6 +313,17 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::array:
|
||||
{
|
||||
const bool object = n->kind == node_kind::object;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// (moved containers are also new)
|
||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
positions.set(std::size_t{n->off}, std::size_t{n->off} + 1);
|
||||
}
|
||||
#endif
|
||||
if (object)
|
||||
{
|
||||
sax.start_object(n->len);
|
||||
@@ -118,7 +340,13 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
// (with positions: until an element is read, the container's
|
||||
// own bracket is the last thing read)
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
, positions.start, positions.end
|
||||
#endif
|
||||
};
|
||||
break;
|
||||
}
|
||||
case value_t::string:
|
||||
@@ -159,6 +387,31 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
frame& f = open[depth - 1];
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
if (f.start == std::string::npos || f.last_end == std::string::npos)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// the closing bracket, as the lexer reports it (end_object()
|
||||
// and end_array() only read its end; the start of the
|
||||
// container was set when it was opened)
|
||||
positions.set(end - 1, end);
|
||||
}
|
||||
}
|
||||
if (depth > 1)
|
||||
{
|
||||
open[depth - 2].last_end = positions.end;
|
||||
}
|
||||
#endif
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
|
||||
@@ -69,7 +69,7 @@ struct node
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown)
|
||||
};
|
||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
||||
|
||||
|
||||
Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user