From 908e3cd6ca62daacc03696c3afb879fd623063fa Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 25 Oct 2026 09:47:47 +0100 Subject: [PATCH 001/195] tools: do not lint GYP JS files They are not maintained in this repository. Signed-off-by: Antoine du Hamel PR-URL: https://github.com/nodejs/node/pull/66988 Reviewed-By: Antoine du Hamel Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index c19e8f3cf46e..9c4ad8f05651 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -57,6 +57,7 @@ export default [ 'lib/punycode.js', 'test/.tmp.*/**', 'test/addons/??_*', + 'tools/gyp/**', // We want to lint only a few specific fixtures folders 'test/fixtures/*', From b6e024af18dbe288d1efc46a1efc093cd8cc0916 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Sun, 25 Oct 2026 00:54:19 +0000 Subject: [PATCH 002/195] tools: update gyp-next to 0.22.4 PR-URL: https://github.com/nodejs/node/pull/66988 Reviewed-By: Antoine du Hamel Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- tools/gyp/CHANGELOG.md | 10 ++++ ...itlint.config.mjs => commitlint.config.js} | 0 tools/gyp/pylib/gyp/common.py | 8 ++- tools/gyp/pylib/gyp/common_test.py | 12 +++++ tools/gyp/pylib/gyp/generator/make.py | 40 +++++++++++--- tools/gyp/pylib/gyp/generator/ninja.py | 24 +++++++-- tools/gyp/pylib/gyp/xcode_emulation.py | 5 +- tools/gyp/pyproject.toml | 2 +- tools/gyp/test/fixtures/pch_action.h | 1 + tools/gyp/test/fixtures/pch_asm.S | 2 + tools/gyp/test/fixtures/pch_dep.h | 1 + tools/gyp/test/fixtures/pch_prefix.gyp | 47 +++++++++++++++++ tools/gyp/test/integration_test.py | 52 +++++++++++++++++++ 13 files changed, 189 insertions(+), 15 deletions(-) rename tools/gyp/{commitlint.config.mjs => commitlint.config.js} (100%) create mode 100644 tools/gyp/test/fixtures/pch_action.h create mode 100644 tools/gyp/test/fixtures/pch_asm.S create mode 100644 tools/gyp/test/fixtures/pch_dep.h create mode 100644 tools/gyp/test/fixtures/pch_prefix.gyp diff --git a/tools/gyp/CHANGELOG.md b/tools/gyp/CHANGELOG.md index e590b6fdb8c8..c0f014ba294a 100644 --- a/tools/gyp/CHANGELOG.md +++ b/tools/gyp/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.22.4](https://github.com/nodejs/gyp-next/compare/v0.22.3...v0.22.4) (2026-10-20) + + +### Bug Fixes + +* avoid deprecated os.path.commonprefix() in RelativePath ([#365](https://github.com/nodejs/gyp-next/issues/365)) ([428343e](https://github.com/nodejs/gyp-next/commit/428343ee58c05e7b817d3603688fe7cdb7a10846)), closes [#364](https://github.com/nodejs/gyp-next/issues/364) +* build precompiled headers after generated inputs ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) +* include the prefix header itself in assembly sources ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) +* **make:** compile the C prefix header with CFLAGS ([#360](https://github.com/nodejs/gyp-next/issues/360)) ([d8888e9](https://github.com/nodejs/gyp-next/commit/d8888e932040eae90604b77ec6030a25714f0077)) + ## [0.22.3](https://github.com/nodejs/gyp-next/compare/v0.22.2...v0.22.3) (2026-09-29) diff --git a/tools/gyp/commitlint.config.mjs b/tools/gyp/commitlint.config.js similarity index 100% rename from tools/gyp/commitlint.config.mjs rename to tools/gyp/commitlint.config.js diff --git a/tools/gyp/pylib/gyp/common.py b/tools/gyp/pylib/gyp/common.py index 223ce47b0032..c034f69c0af7 100644 --- a/tools/gyp/pylib/gyp/common.py +++ b/tools/gyp/pylib/gyp/common.py @@ -157,8 +157,12 @@ def RelativePath(path, relative_to, follow_path_symlink=True): path_split = path.split(os.path.sep) relative_to_split = relative_to.split(os.path.sep) - # Determine how much of the prefix the two paths share. - prefix_len = len(os.path.commonprefix([path_split, relative_to_split])) + # Determine how much of the path prefix the two paths share. + prefix_len = 0 + for path_part, relative_to_part in zip(path_split, relative_to_split): + if path_part != relative_to_part: + break + prefix_len += 1 # Put enough ".." components to back up out of relative_to to the common # prefix, and then append the part of path_split after the common prefix. diff --git a/tools/gyp/pylib/gyp/common_test.py b/tools/gyp/pylib/gyp/common_test.py index b5988816c04a..d3e860534de6 100755 --- a/tools/gyp/pylib/gyp/common_test.py +++ b/tools/gyp/pylib/gyp/common_test.py @@ -182,5 +182,17 @@ def mock_run(env, defines_stdout, expected_cmd, throws=False): assert flavor6 == "wasi" +class TestRelativePath(unittest.TestCase): + def test_common_parent(self): + assert gyp.common.RelativePath("/a/b", "/a/c") == os.path.join("..", "b") + + def test_only_root_in_common(self): + """Paths whose only common ancestor is the filesystem root.""" + assert gyp.common.RelativePath("/a/b", "/c") == os.path.join("..", "a", "b") + + def test_same_path(self): + assert gyp.common.RelativePath("/a/b", "/a/b") == "" + + if __name__ == "__main__": unittest.main() diff --git a/tools/gyp/pylib/gyp/generator/make.py b/tools/gyp/pylib/gyp/generator/make.py index 16b6f4e80b11..c47050a52285 100644 --- a/tools/gyp/pylib/gyp/generator/make.py +++ b/tools/gyp/pylib/gyp/generator/make.py @@ -603,7 +603,7 @@ def CalculateGeneratorInputInfo(params): # Commands for precompiled header files. quiet_cmd_pch_c = CXX($(TOOLSET)) $@ -cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< +cmd_pch_c = $(CC.$(TOOLSET)) $(GYP_PCH_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_cc = CXX($(TOOLSET)) $@ cmd_pch_cc = $(CC.$(TOOLSET)) $(GYP_PCH_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $< quiet_cmd_pch_m = CXX($(TOOLSET)) $@ @@ -1485,13 +1485,29 @@ def WriteSources( # See "Target-specific Variable Values" in the GNU Make manual.""" ) self.WriteLn("$(OBJS): TOOLSET := $(TOOLSET)") - self.WriteLn( - "$(OBJS): GYP_CFLAGS := " + gyp_cflags = ( "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " - "%s " % precompiled_header.GetInclude("c") + "$(CFLAGS_$(BUILDTYPE)) " + "%s " + "$(CFLAGS_$(BUILDTYPE)) " "$(CFLAGS_C_$(BUILDTYPE))" ) + self.WriteLn( + "$(OBJS): GYP_CFLAGS := " + + gyp_cflags % precompiled_header.GetInclude("c") + ) + asm_objs = [ + obj + for source, obj in zip(compilable, objs) + if source.endswith((".s", ".S")) + ] + if asm_objs and precompiled_header.compile_headers: + # Assembly is compiled with GYP_CFLAGS too, but without a gch + # for it, the -include of the C one fails. + self.WriteLn( + "%s: GYP_CFLAGS := " % " ".join(asm_objs) + + gyp_cflags % precompiled_header.GetInclude("asm") + ) self.WriteLn( "$(OBJS): GYP_CXXFLAGS := " "$(DEFS_$(BUILDTYPE)) " @@ -1521,7 +1537,9 @@ def WriteSources( "$(CFLAGS_OBJCC_$(BUILDTYPE))" ) - self.WritePchTargets(precompiled_header.GetPchBuildCommands()) + self.WritePchTargets( + precompiled_header.GetPchBuildCommands(), deps + extra_outputs + ) # If there are any object files in our input file list, link them into our # output. @@ -1529,8 +1547,13 @@ def WriteSources( self.WriteLn() - def WritePchTargets(self, pch_commands): - """Writes make rules to compile prefix headers.""" + def WritePchTargets(self, pch_commands, order_only_deps): + """Writes make rules to compile prefix headers. + + A prefix header may include generated headers, so like the object files, + the gch files are built after |order_only_deps|: the target's dependencies + and the outputs of its actions and rules. + """ if not pch_commands: return @@ -1547,6 +1570,9 @@ def WritePchTargets(self, pch_commands): "m": "GYP_PCH_OBJCFLAGS", "mm": "GYP_PCH_OBJCXXFLAGS", }[lang] + if order_only_deps: + self.WriteMakeRule([gch], order_only_deps, order_only=True) + self.WriteLn(f"{gch}: TOOLSET := $(TOOLSET)") self.WriteLn( f"{gch}: {var_name} := {lang_flag} " + "$(DEFS_$(BUILDTYPE)) " "$(INCS_$(BUILDTYPE)) " diff --git a/tools/gyp/pylib/gyp/generator/ninja.py b/tools/gyp/pylib/gyp/generator/ninja.py index 3ceaf470cec2..49ce563ea7a4 100644 --- a/tools/gyp/pylib/gyp/generator/ninja.py +++ b/tools/gyp/pylib/gyp/generator/ninja.py @@ -1253,6 +1253,12 @@ def WriteSourcesForArch( cflags_cc, self.ExpandSpecial, ) + elif ext in ("s", "S") and precompiled_header.compile_headers: + # Assembly gets $cflags_pch_c too, but without a gch for it, the + # -include of the C one fails. + variables = [ + ("cflags_pch_c", precompiled_header.GetInclude("asm", arch)) + ] ninja_file.build( output, command, @@ -1274,13 +1280,17 @@ def WriteSourcesForArch( ], ) - self.WritePchTargets(ninja_file, pch_commands) + self.WritePchTargets(ninja_file, pch_commands, predepends) ninja_file.newline() return outputs - def WritePchTargets(self, ninja_file, pch_commands): - """Writes ninja rules to compile prefix headers.""" + def WritePchTargets(self, ninja_file, pch_commands, predepends): + """Writes ninja rules to compile prefix headers. + + A prefix header may include generated headers, so like the object files, + the gch files are built after |predepends|. + """ if not pch_commands: return @@ -1299,7 +1309,13 @@ def WritePchTargets(self, ninja_file, pch_commands): "mm": "objcxx", } cmd = map.get(lang) - ninja_file.build(gch, cmd, input, variables=[(var_name, lang_flag)]) + ninja_file.build( + gch, + cmd, + input, + order_only=predepends, + variables=[(var_name, lang_flag)], + ) def WriteLink(self, spec, config_name, config, link_deps, compile_deps): """Write out a link step. Fills out target.binary.""" diff --git a/tools/gyp/pylib/gyp/xcode_emulation.py b/tools/gyp/pylib/gyp/xcode_emulation.py index d13eaa9af240..7ae3c4f9c42d 100644 --- a/tools/gyp/pylib/gyp/xcode_emulation.py +++ b/tools/gyp/pylib/gyp/xcode_emulation.py @@ -1429,7 +1429,10 @@ def _CompiledHeader(self, lang, arch): return h def GetInclude(self, lang, arch=None): - """Gets the cflags to include the prefix header for language |lang|.""" + """Gets the cflags to include the prefix header for language |lang|. + + There is no compiled header for assembly ('asm'), which includes the + prefix header itself.""" if self.compile_headers and lang in self.compiled_headers: return "-include %s" % self._CompiledHeader(lang, arch) elif self.header: diff --git a/tools/gyp/pyproject.toml b/tools/gyp/pyproject.toml index 9ce52a6785e4..71c24110cd4f 100644 --- a/tools/gyp/pyproject.toml +++ b/tools/gyp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "gyp-next" -version = "0.22.3" +version = "0.22.4" authors = [ { name="Node.js contributors", email="ryzokuken@disroot.org" }, ] diff --git a/tools/gyp/test/fixtures/pch_action.h b/tools/gyp/test/fixtures/pch_action.h new file mode 100644 index 000000000000..5e3d629b3e2b --- /dev/null +++ b/tools/gyp/test/fixtures/pch_action.h @@ -0,0 +1 @@ +#include "action_generated.h" diff --git a/tools/gyp/test/fixtures/pch_asm.S b/tools/gyp/test/fixtures/pch_asm.S new file mode 100644 index 000000000000..84d6417a91d6 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_asm.S @@ -0,0 +1,2 @@ +// Assembly gets the prefix header too, but has no precompiled header. +.text diff --git a/tools/gyp/test/fixtures/pch_dep.h b/tools/gyp/test/fixtures/pch_dep.h new file mode 100644 index 000000000000..7df152ab9b12 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_dep.h @@ -0,0 +1 @@ +#include "dep_generated.h" diff --git a/tools/gyp/test/fixtures/pch_prefix.gyp b/tools/gyp/test/fixtures/pch_prefix.gyp new file mode 100644 index 000000000000..c505559c7610 --- /dev/null +++ b/tools/gyp/test/fixtures/pch_prefix.gyp @@ -0,0 +1,47 @@ +{ + 'targets': [ + { + # The prefix header includes a header generated by the target's action. + 'target_name': 'pch_action', + 'type': 'static_library', + 'sources': [ 'pch.cc', 'pch_asm.S' ], + 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)' ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'pch_action.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + 'actions': [ + { + 'action_name': 'generate_action_header', + 'inputs': [], + 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/action_generated.h' ], + 'action': [ 'touch', '<@(_outputs)' ], + }, + ], + }, + { + # The prefix header includes a header generated by a dependency. + 'target_name': 'pch_dep', + 'type': 'static_library', + 'dependencies': [ 'generate_dep_header' ], + 'sources': [ 'pch.cc', 'pch_asm.S' ], + 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)' ], + 'xcode_settings': { + 'GCC_PREFIX_HEADER': 'pch_dep.h', + 'GCC_PRECOMPILE_PREFIX_HEADER': 'YES', + }, + }, + { + 'target_name': 'generate_dep_header', + 'type': 'none', + 'actions': [ + { + 'action_name': 'generate_dep_header', + 'inputs': [], + 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/dep_generated.h' ], + 'action': [ 'touch', '<@(_outputs)' ], + }, + ], + }, + ], +} diff --git a/tools/gyp/test/integration_test.py b/tools/gyp/test/integration_test.py index 8244708a9bd5..c93c8669482c 100644 --- a/tools/gyp/test/integration_test.py +++ b/tools/gyp/test/integration_test.py @@ -5,6 +5,7 @@ import os import re import shutil +import subprocess import sys import unittest @@ -13,6 +14,7 @@ fixture_dir = os.path.join(os.path.dirname(__file__), "fixtures") gyp_file = os.path.join(fixture_dir, "integration.gyp") pch_gyp_file = os.path.join(fixture_dir, "pch.gyp") +pch_prefix_gyp_file = os.path.join(fixture_dir, "pch_prefix.gyp") if sys.platform == "win32": sysname = sys.platform @@ -80,6 +82,56 @@ def test_cmake(self) -> None: assert_file(self, "out/Default/CMakeLists.txt", "cmake/CMakeLists.txt") +class TestGypMac(unittest.TestCase): + # The prefix header of each target includes a header generated by its + # action (pch_action) or by its dependency (pch_dep). Each output is built + # on its own from a clean build, so it fails unless it waits for that header. + pch_targets = ("pch_action", "pch_dep") + pch_langs = ("c", "cc", "m", "mm") + + def setUp(self) -> None: + if sysname != "darwin": + self.skipTest("macOS-only test") + shutil.rmtree(os.path.join(fixture_dir, "out"), ignore_errors=True) + + def test_make_precompiled_prefix_header(self) -> None: + rc = gyp.main( + [ + "-f", + "make", + "--depth", + fixture_dir, + "--generator-output", + "out", + pch_prefix_gyp_file, + ] + ) + assert rc == 0 + + makefile_dir = os.path.join(fixture_dir, "out") + # CXXFLAGS that a C compile rejects check the C gch doesn't get them. + env = {**os.environ, "CXXFLAGS": "-std=c++17"} + for target in self.pch_targets: + obj = f"out/Default/obj.target/{target}" + gchs = [f"{obj}/pch-{lang}/{target}.h.gch" for lang in self.pch_langs] + for output in [*gchs, f"{obj}/pch_asm.o"]: + shutil.rmtree(os.path.join(makefile_dir, "out"), ignore_errors=True) + subprocess.run( + ["make", "-C", makefile_dir, output], env=env, check=True + ) + + def test_ninja_precompiled_prefix_header(self) -> None: + rc = gyp.main(["-f", "ninja", "--depth", fixture_dir, pch_prefix_gyp_file]) + assert rc == 0 + + build_dir = os.path.join(fixture_dir, "out", "Default") + for target in self.pch_targets: + gchs = [f"obj/{target}.{target}.h-{lang}.gch" for lang in self.pch_langs] + for output in [*gchs, f"obj/{target}.pch_asm.o"]: + subprocess.run(["ninja", "-C", build_dir, "-t", "clean"], check=True) + subprocess.run(["ninja", "-C", build_dir, output], check=True) + + class TestGypWindows(unittest.TestCase): def setUp(self) -> None: if sys.platform != "win32": From b08c226c6a921d419f96a3123758570e8b5b766f Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 27 Oct 2026 17:17:42 -0400 Subject: [PATCH 003/195] test: update test426-fixtures to fae1fa49815813088c433e8486e330e8d953a44 PR-URL: https://github.com/nodejs/node/pull/66815 Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca Reviewed-By: Antoine du Hamel --- .../fixtures/test426/.github/workflows/ci.yml | 70 +++ test/fixtures/test426/.gitignore | 6 + test/fixtures/test426/README.md | 39 ++ .../debug-id/debug-id-index-section.map | 4 +- .../debug-id-index-section.map.golden | 2 +- .../decoding/debug-id/debug-id-index.map | 4 +- .../test426/decoding/debug-id/debug-id.d.ts | 2 +- test/fixtures/test426/npm-package/README.md | 5 + .../test426/npm-package/package.template.json | 26 ++ .../test426/npm-package/types/index.d.ts | 2 + .../test426/npm-package/types/modules.d.ts | 14 + .../test426/npm-package/types/types.d.ts | 90 ++++ test/fixtures/test426/package-lock.json | 405 ++++++++++++++++++ test/fixtures/test426/package.json | 11 + .../test426/scripts/build-npm-package.js | 240 +++++++++++ test/fixtures/test426/scripts/check-types.js | 150 +++++++ test/test426/README.md | 2 +- 17 files changed, 1065 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/test426/.github/workflows/ci.yml create mode 100644 test/fixtures/test426/.gitignore create mode 100644 test/fixtures/test426/npm-package/README.md create mode 100644 test/fixtures/test426/npm-package/package.template.json create mode 100644 test/fixtures/test426/npm-package/types/index.d.ts create mode 100644 test/fixtures/test426/npm-package/types/modules.d.ts create mode 100644 test/fixtures/test426/npm-package/types/types.d.ts create mode 100644 test/fixtures/test426/package-lock.json create mode 100644 test/fixtures/test426/package.json create mode 100644 test/fixtures/test426/scripts/build-npm-package.js create mode 100644 test/fixtures/test426/scripts/check-types.js diff --git a/test/fixtures/test426/.github/workflows/ci.yml b/test/fixtures/test426/.github/workflows/ci.yml new file mode 100644 index 000000000000..72d33dddb54c --- /dev/null +++ b/test/fixtures/test426/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Validate tests and build npm package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + # Fetch all to count number of commits and compute pkg version + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + cache: npm + - name: Install dependencies + run: npm ci + - name: Build npm package + run: node scripts/build-npm-package.js + - name: Check that JSON files match their TypeScript types + run: node scripts/check-types.js + - name: Pack npm package + run: mkdir -p dist && npm pack ./npm-package --pack-destination dist + - name: Upload package tarball + uses: actions/upload-artifact@v7 + with: + name: npm-package + path: dist/*.tgz + if-no-files-found: error + retention-days: 7 + + publish: + name: Publish to npm + needs: build + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository == 'tc39/source-map-tests' + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # for npm trusted publishing + concurrency: + group: npm-publish + cancel-in-progress: false + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: actions/setup-node@v6 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org + - name: Build npm package + run: node scripts/build-npm-package.js + - name: Publish + run: | + name=$(node -p "require('./npm-package/package.json').name") + version=$(node -p "require('./npm-package/package.json').version") + if [ -n "$(npm view "$name@$version" version 2>/dev/null)" ]; then + echo "$name@$version is already published, skipping." + else + npm publish ./npm-package --access public + fi diff --git a/test/fixtures/test426/.gitignore b/test/fixtures/test426/.gitignore new file mode 100644 index 000000000000..a311c506246a --- /dev/null +++ b/test/fixtures/test426/.gitignore @@ -0,0 +1,6 @@ +npm-package/* +!npm-package/README.md +!npm-package/package.template.json +!npm-package/types/ +node_modules/ +.tmp/ diff --git a/test/fixtures/test426/README.md b/test/fixtures/test426/README.md index 1eb2818fcfa9..98dcb05da245 100644 --- a/test/fixtures/test426/README.md +++ b/test/fixtures/test426/README.md @@ -58,3 +58,42 @@ More information about running Chrome Devtools without building Chromium can be Follow the [running Node.js tests guide](https://github.com/nodejs/node/blob/main/BUILDING.md#running-tests) to build Node.js and run tests locally. Read more about running the source map tests at [here](https://github.com/nodejs/node/tree/main/test/test426). + +## npm package + +The test cases are published to npm as [`@tc39/source-map-tests`](https://www.npmjs.com/package/@tc39/source-map-tests), so that source map implementations can run them in their own test suites. A new version is published for every commit to `main`. + +```sh +npm install --save-dev --save-exact @tc39/source-map-tests +``` + +### Usage + +`@tc39/source-map-tests/suites.json` lists the test suites: + +```json +{ + "suites": [ + { "kind": "interpreting", "file": "interpreting/source-map-spec.json" }, + { "kind": "decoding", "file": "decoding/scopes.json" } + ] +} +``` + +There are two types of test suites: `interpreting` and `decoding`: +- **interpreting** provided a source map to apply to a generated file, together with some actions to perform through it to check that it has been applied correctly. +- **decoding** suites check that a source map is correctly decoded into a [Decoded Source Map Record](https://tc39.es/ecma426/#decoded-source-map-record). + +### TypeScript + +The package's root exports the types of the JSON files: + +```ts +import type { InterpretingSuite, DecodingSuite, TestAction } from "@tc39/source-map-tests"; +``` + +With TypeScript 7.1 or later, you can load type definitions for the various suites by adding `"types": ["@tc39/source-map-tests"]` to your `tsconfig.json`. Imported JSON modules then will be typed appropriately: +```ts +import spec from "@tc39/source-map-tests/interpreting/source-map-spec.json" with { type: "json" }; +spec.tests[0].testActions; // TestAction[] +``` diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map index 9c6f3fbde1ed..7a6d25afc73f 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map @@ -4,7 +4,7 @@ "sections": [{ "offset": { "line": 0, - "column": 0, + "column": 0 }, "map": { "version": 3, @@ -16,7 +16,7 @@ }, { "offset": { "line": 1, - "column": 0, + "column": 0 }, "map": { "version": 3, diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden index 7c3a8456e6b0..69f6ebf9dcb9 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index-section.map.golden @@ -1,5 +1,5 @@ { "file": "app.js", "sources": [], - "mappings": [], + "mappings": [] } \ No newline at end of file diff --git a/test/fixtures/test426/decoding/debug-id/debug-id-index.map b/test/fixtures/test426/decoding/debug-id/debug-id-index.map index 11de486e1a9a..0b7231929e4b 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id-index.map +++ b/test/fixtures/test426/decoding/debug-id/debug-id-index.map @@ -5,7 +5,7 @@ "sections": [{ "offset": { "line": 0, - "column": 0, + "column": 0 }, "map": { "version": 3, @@ -17,7 +17,7 @@ }, { "offset": { "line": 1, - "column": 0, + "column": 0 }, "map": { "version": 3, diff --git a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts index 1f9862f012ec..526b584ca199 100644 --- a/test/fixtures/test426/decoding/debug-id/debug-id.d.ts +++ b/test/fixtures/test426/decoding/debug-id/debug-id.d.ts @@ -2,5 +2,5 @@ /// interface SourceMapRecord { - debugId: string|null; + debugId?: string|null; } diff --git a/test/fixtures/test426/npm-package/README.md b/test/fixtures/test426/npm-package/README.md new file mode 100644 index 000000000000..11012332c544 --- /dev/null +++ b/test/fixtures/test426/npm-package/README.md @@ -0,0 +1,5 @@ +# @tc39/source-map-tests + +Cross-implementation test cases for the [ECMA-426 source map specification](https://tc39.es/ecma426/). + +See the [tc39/source-map-tests README](https://github.com/tc39/source-map-tests#npm-package) for the package contents and usage. diff --git a/test/fixtures/test426/npm-package/package.template.json b/test/fixtures/test426/npm-package/package.template.json new file mode 100644 index 000000000000..68c0ae4ac298 --- /dev/null +++ b/test/fixtures/test426/npm-package/package.template.json @@ -0,0 +1,26 @@ +{ + "name": "@tc39/source-map-tests", + "version": "X.X.X", + "description": "Cross-implementation test cases for the ECMA-426 source map specification", + "license": "BSD-3-Clause", + "repository": { + "type": "git", + "url": "git+https://github.com/tc39/source-map-tests.git" + }, + "homepage": "https://github.com/tc39/source-map-tests#readme", + "bugs": "https://github.com/tc39/source-map-tests/issues", + "keywords": [ + "source-map", + "sourcemap", + "ecma-426", + "test-suite", + "tests" + ], + "files": [ + "suites.json", + "interpreting/", + "decoding/", + "types/", + "LICENSE.md" + ] +} diff --git a/test/fixtures/test426/npm-package/types/index.d.ts b/test/fixtures/test426/npm-package/types/index.d.ts new file mode 100644 index 000000000000..658794c6f1d0 --- /dev/null +++ b/test/fixtures/test426/npm-package/types/index.d.ts @@ -0,0 +1,2 @@ +/// +export * from "./types.js"; diff --git a/test/fixtures/test426/npm-package/types/modules.d.ts b/test/fixtures/test426/npm-package/types/modules.d.ts new file mode 100644 index 000000000000..08300745affe --- /dev/null +++ b/test/fixtures/test426/npm-package/types/modules.d.ts @@ -0,0 +1,14 @@ +declare module "@tc39/source-map-tests/suites*.json" with { type: "json" } { + const index: import("./types.js").SuitesIndex; + export default index; +} + +declare module "@tc39/source-map-tests/interpreting/*.json" with { type: "json" } { + const suite: import("./types.js").InterpretingSuite; + export default suite; +} + +declare module "@tc39/source-map-tests/decoding/*.json" with { type: "json" } { + const suite: import("./types.js").DecodingSuite; + export default suite; +} diff --git a/test/fixtures/test426/npm-package/types/types.d.ts b/test/fixtures/test426/npm-package/types/types.d.ts new file mode 100644 index 000000000000..7cbe9d48892c --- /dev/null +++ b/test/fixtures/test426/npm-package/types/types.d.ts @@ -0,0 +1,90 @@ +/** + * Types for the JSON files in this package. + * + * All paths are relative to the package root. + */ + +/** The format of `suites.json`. */ +export interface SuitesIndex { + suites: SuiteInfo[]; +} + +export type SuiteInfo = + | { kind: "interpreting"; file: string } + | { kind: "decoding"; file: string }; + +/** A file from the test suite, with its contents inlined. */ +export interface TestFile { + path: string; + content: string; +} + +/** The format of interpreting suite files (`interpreting/.json`). */ +export interface InterpretingSuite { + tests: InterpretingTest[]; +} + +export interface InterpretingTest { + name: string; + description: string; + sourceMapIsValid: boolean; + testActions: TestAction[]; + /** + * The transpiled source that the source map refers to. + */ + baseFile: TestFile; + sourceMapFile: TestFile; +} + +export type TestAction = + | CheckMappingAction + | CheckMappingTransitiveAction + | CheckIgnoreListAction; + +export interface CheckMappingAction { + actionType: "checkMapping"; + /** 0-based. */ + generatedLine: number; + /** 0-based. */ + generatedColumn: number; + originalSource: string | null; + /** 0-based. */ + originalLine: number | null; + /** 0-based. */ + originalColumn: number | null; + mappedName: string | null; +} + +export interface CheckMappingTransitiveAction + extends Omit { + actionType: "checkMappingTransitive"; + /** Source maps to apply after `sourceMapFile`, in order. */ + intermediateMaps: TestFile[]; +} + +export interface CheckIgnoreListAction { + actionType: "checkIgnoreList"; + /** Sources that must be in the ignore list. */ + present: string[]; +} + +/** The format of decoding suite files (`decoding/.json`). */ +export interface DecodingSuite { + tests: DecodingTest[]; +} + +export interface DecodingTest { + name: string; + sourceMapFile: TestFile; + /** + * The expected Decoded Source Map Record, as JSON in the format described by + * `decoding/source-map-record.d.ts` (and extended by the proposal-specific + * `decoding//.d.ts` files). + */ + goldenFile: TestFile; + /** + * Whether decoding this source map should optionally report an error + * (https://tc39.es/ecma426/#sec-optional-errors). + */ + optionalErrors: boolean; +} diff --git a/test/fixtures/test426/package-lock.json b/test/fixtures/test426/package-lock.json new file mode 100644 index 000000000000..daf25a482c9f --- /dev/null +++ b/test/fixtures/test426/package-lock.json @@ -0,0 +1,405 @@ +{ + "name": "source-map-tests", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "typescript": "7.1.0-beta" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.1.0-beta.tgz", + "integrity": "sha512-DXDG+jIWxBHGdoO70tcHON33rCnSzsEZj9P13oyf/r/KEGIo6awsdSNVNjAw0uMwEpqRu75IrgCcW58YcVkZHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-android-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-android-arm64/-/typescript-android-arm64-7.1.0-beta.tgz", + "integrity": "sha512-UZx6PxoQavuvaVTrSf3aw0Oy/J/uJpRylipA0kzc038jMvZXh0UtE8vnQp20FqtdgD5bhXZ0aJrRJyoTSUO58Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.1.0-beta.tgz", + "integrity": "sha512-q7mxblcyX0E7hXsY4k+Q6KWKK5mGmfVq+BDesdTgRVgjodk27aIxER77Zyj9+TgSTlCje8F7+JM6f6hl2BuNdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.1.0-beta.tgz", + "integrity": "sha512-D5EYqG5Wm/guw9+0j8+Mn1Jbr/gfS/VtFTuoH8PJtFbcqeDaQGMulc7SW8q51QG6Y3wGOGX3nbsTOw6fIKWWAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-9A0XX0tcnH9qPdolSKKzSjTb93dlB0UEcAJbHYEsrFX2fkFNSum9+dvkQQJ9vLc+7vxHetDPujq9Qe6wx+Xhow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-EDf/dBHfY0TxX3NmUTek0hbffeyZlh3+ls6WQW3ICB8vYxwYLFnzg7wkgEUvl9eRlpiJ/nmc2S8arPMzINJ7rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.1.0-beta.tgz", + "integrity": "sha512-AvN9HmzOQh46G4312ooSgMHhbB7DtnLwg8D9mo324X49B7weMwtJrBG1MxT9IyI8kzDd7YRtW+1hWGun7WG7Qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.1.0-beta.tgz", + "integrity": "sha512-hNqQBgQ5b5oGqN76QqbAU6CwYhJZ6iOcWc2kPTUs2OentgpxZktETziH6KIyRFJPaiBncognwn2ZLu3DNhHiWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.1.0-beta.tgz", + "integrity": "sha512-xbUustlBrFflt2pNEyG5ye+d4WGPc/rfr2xK29MXeobPSw297JqwuKRrvbKM5o3fE/eUM/c+urVm6jQ3IaMVTw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.1.0-beta.tgz", + "integrity": "sha512-fS4/G7/0bXZ3NSq3nBMAXyg65I3tO4S7j08VNQ2g1aNC/qq79gE7Qne1BNjVsjMFfHBVBFjQYoTQCjmVU2iFlg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.1.0-beta.tgz", + "integrity": "sha512-0upCCxYPX8X6gOwkvfv+Ee7/ufyy7k97mTrGE0e5BxLlnUEa+w+rtgnv2dsKcxFdwdeZYVlZKCqgN/xCBV08+A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.1.0-beta.tgz", + "integrity": "sha512-0jKU9YRgOPRgiy5V/UWv37kB53PaTpIbwGCbbOMUPPAkrjVRNOIAJaz7Wa7s3H7mdDebX1LopUEPEJR+7Xa7zg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.1.0-beta.tgz", + "integrity": "sha512-j+d04s2kxaPQh6DLPb01sknAQfzsrdSZy15jBRHyLEZoaaQ19dVjbFRlkNVvYwUG7ZdP8+y5b/7RmPxdtHUKdQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.1.0-beta.tgz", + "integrity": "sha512-kcD51ihf+pnI5KT4uQQYG04OpwznaZYBlIQhDhl8fflFd27NktxXsUeguxZyvUMyWfum7IF2bBS6Jv7ox0AgFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-8W85YyoGK+rgGoSU9jJQUpELbrKcZcbdJk2jPNQ6UKDKI7ryPlPFTrV2jCE5RQuv4Tt96hMDTc33s4YfRrLFBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-iGsTE0ve2PG0AcyZinwAR0LnGEZhu/y4dceS0hZsYCaKdYETvQtvtlNWlmYKempsBr6C7Il7dMXHVk+VFm9u0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.1.0-beta.tgz", + "integrity": "sha512-Z2FERdHB9fAKI+KGqdWn04O7uNlnB+VYiEm6INE1aZuzzLNd3KBqQ6IpKUJF2ny4GigGU6nVu75XD9tAUODPng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.1.0-beta.tgz", + "integrity": "sha512-NVG3qeyCq/rJEOhaRjR3LsWgjrjo5vJW25omVLeHAVnpKIG4pE4cenwwKGR06DiJ1s0QTOymcJeKXMEpsiNBVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.1.0-beta.tgz", + "integrity": "sha512-ovAdoWXEn2B5/fyWG69pLU3FWmQwPWOjQV5TFbcxtpnOv+R3g8ySGHl1NYRq7Lb/GYdFDvaGcZ+SiU7iKuiMsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.1.0-beta.tgz", + "integrity": "sha512-lJbo71IEOfL7ZZIZnArRaOLqjn9BAfoSzyUQn0aSjfSpDZL4ru0afUsdBfo6PQEjD6ssdmi0n/RwaPADX6bsvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.1.0-beta.tgz", + "integrity": "sha512-UKcHvbybY2tDTvFW2b1vwiAvDlIRooo69lh8CXWVhOQejcvpM5+IgbiFW9EIwR5Wi5X/v+BdN7gdWbigBuJ3rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/typescript": { + "version": "7.1.0-beta", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.1.0-beta.tgz", + "integrity": "sha512-cjWjZAJDsmZzsNC5GwHNlW0YPM65jZa5oR0KyQcYX3KSVIfTvtBq//vewISyatKk4OLi5grZjHhIVE9fdrw1UQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.1.0-beta", + "@typescript/typescript-android-arm64": "7.1.0-beta", + "@typescript/typescript-darwin-arm64": "7.1.0-beta", + "@typescript/typescript-darwin-x64": "7.1.0-beta", + "@typescript/typescript-freebsd-arm64": "7.1.0-beta", + "@typescript/typescript-freebsd-x64": "7.1.0-beta", + "@typescript/typescript-linux-arm": "7.1.0-beta", + "@typescript/typescript-linux-arm64": "7.1.0-beta", + "@typescript/typescript-linux-loong64": "7.1.0-beta", + "@typescript/typescript-linux-mips64el": "7.1.0-beta", + "@typescript/typescript-linux-ppc64": "7.1.0-beta", + "@typescript/typescript-linux-riscv64": "7.1.0-beta", + "@typescript/typescript-linux-s390x": "7.1.0-beta", + "@typescript/typescript-linux-x64": "7.1.0-beta", + "@typescript/typescript-netbsd-arm64": "7.1.0-beta", + "@typescript/typescript-netbsd-x64": "7.1.0-beta", + "@typescript/typescript-openbsd-arm64": "7.1.0-beta", + "@typescript/typescript-openbsd-x64": "7.1.0-beta", + "@typescript/typescript-sunos-x64": "7.1.0-beta", + "@typescript/typescript-win32-arm64": "7.1.0-beta", + "@typescript/typescript-win32-x64": "7.1.0-beta" + } + } + } +} diff --git a/test/fixtures/test426/package.json b/test/fixtures/test426/package.json new file mode 100644 index 000000000000..f760360f8fe1 --- /dev/null +++ b/test/fixtures/test426/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "type": "module", + "scripts": { + "build": "node scripts/build-npm-package.js", + "check-types": "node scripts/build-npm-package.js && node scripts/check-types.js" + }, + "devDependencies": { + "typescript": "7.1.0-beta" + } +} diff --git a/test/fixtures/test426/scripts/build-npm-package.js b/test/fixtures/test426/scripts/build-npm-package.js new file mode 100644 index 000000000000..7570fefce1d6 --- /dev/null +++ b/test/fixtures/test426/scripts/build-npm-package.js @@ -0,0 +1,240 @@ +// Builds the contents for the @tc39/source-map-tests npm package, +// in the `npm-package` folder. + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const outDir = path.join(root, "npm-package"); +const committedFiles = new Set([ + "README.md", + "package.template.json", + "types", +]); + +const errors = []; +const error = msg => errors.push(msg); + +const rel = abs => path.relative(root, abs).split(path.sep).join("/"); +const abs = relPath => path.join(root, relPath); +const out = relPath => path.join(outDir, relPath); + +function walk(dir) { + return fs + .readdirSync(dir, { withFileTypes: true }) + .flatMap(e => + e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)] + ); +} + +/** + * Returns `{ path, content }` for a repo-relative path, or null if missing. + * `packagePath` is where the file ends up in the npm package. + */ +function fileEntry(relPath, context, packagePath = relPath) { + if (!fs.existsSync(abs(relPath))) { + error(`${context}: referenced file ${relPath} does not exist`); + return null; + } + return { path: packagePath, content: fs.readFileSync(abs(relPath), "utf8") }; +} + +function checkUniqueNames(suiteName, tests) { + const seen = new Set(); + for (const { name } of tests) { + if (seen.has(name)) error(`${suiteName}: duplicate test name ${name}`); + seen.add(name); + } +} + +// Interpreting suites: [-proposal]-tests.json manifests in the repo root, +// whose files live in resources/. In the package, resources/ +// is copied to interpreting/resources/. + +const referencedResources = new Set(); + +function buildInterpretingSuite(manifestFile) { + const name = manifestFile.replace(/-tests\.json$/, "").replace(/-proposal$/, ""); + const manifest = JSON.parse(fs.readFileSync(abs(manifestFile), "utf8")); + const base = path.posix.join("resources", manifest.resourceBasePath ?? ""); + const resource = (file, context) => { + const relPath = path.posix.join(base, file); + referencedResources.add(relPath); + return fileEntry(relPath, context, `interpreting/${relPath}`); + }; + + const tests = manifest.tests.map(test => { + const context = `${manifestFile} (${test.name})`; + return { + name: test.name, + description: test.description, + sourceMapIsValid: test.sourceMapIsValid, + baseFile: resource(test.baseFile, context), + sourceMapFile: resource(test.sourceMapFile, context), + testActions: (test.testActions ?? []).map(action => + action.intermediateMaps + ? { + ...action, + intermediateMaps: action.intermediateMaps.map(m => + resource(m, context) + ), + } + : action + ), + }; + }); + checkUniqueNames(name, tests); + return { name, kind: "interpreting", tests }; +} + +// Decoding suites: each directory under decoding/ containing .map files, +// each with a .map.golden and an optional .error/.map.error. + +function buildDecodingSuite(dir) { + const name = path.basename(dir); + const files = fs.readdirSync(dir).sort(); + const used = new Set(); + const tests = []; + + for (const file of files.filter(f => f.endsWith(".map"))) { + const testName = file.slice(0, -".map".length); + const context = `${rel(dir)} (${testName})`; + const sourceMapFile = fileEntry(rel(path.join(dir, file)), context); + const goldenFile = fileEntry(rel(path.join(dir, `${file}.golden`)), context); + const errorFile = [`${testName}.error`, `${file}.error`].find(f => + files.includes(f) + ); + used.add(file).add(`${file}.golden`).add(errorFile); + + if (goldenFile) { + try { + JSON.parse(goldenFile.content); + } catch (e) { + error(`${goldenFile.path}: invalid JSON (${e.message})`); + } + } + tests.push({ + name: testName, + sourceMapFile, + goldenFile, + optionalErrors: errorFile !== undefined, + }); + } + + for (const file of files) { + if (/\.(golden|error)$/.test(file) && !used.has(file)) { + error(`${rel(path.join(dir, file))}: no matching .map file`); + } + } + + checkUniqueNames(name, tests); + return { name, kind: "decoding", tests }; +} + +// Stable spec tests come first, followed by proposals. +const isProposal = f => f.endsWith("-proposal-tests.json"); +const interpretingSuites = fs + .readdirSync(root) + .filter(f => f.endsWith("-tests.json")) + .sort((a, b) => isProposal(a) - isProposal(b) || a.localeCompare(b)) + .map(buildInterpretingSuite); + +const decodingDirs = [ + path.join(root, "decoding"), + ...fs + .readdirSync(path.join(root, "decoding"), { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => path.join(root, "decoding", e.name)) + .sort(), +]; +const decodingSuites = decodingDirs + .filter(dir => fs.readdirSync(dir).some(f => f.endsWith(".map"))) + .map(buildDecodingSuite); + +const suites = [...interpretingSuites, ...decodingSuites]; + +// Suites go in /.json, next to the directory with their files. +const suiteFile = ({ name, kind }) => `${kind}/${name}.json`; + +const suiteFiles = new Set(); +for (const suite of suites) { + const file = suiteFile(suite); + if (suiteFiles.has(file)) error(`two suites would be written to ${file}`); + suiteFiles.add(file); +} + +for (const file of walk(path.join(root, "resources"))) { + if (file.endsWith(".map") && !referencedResources.has(rel(file))) { + error(`${rel(file)}: source map not referenced by any test`); + } +} + +if (errors.length > 0) { + console.error(`Found ${errors.length} problem(s) in the test data:`); + for (const msg of errors) console.error(` - ${msg}`); + process.exit(1); +} + +// Write the package. + +const writeJSON = (relPath, data) => { + fs.mkdirSync(path.dirname(out(relPath)), { recursive: true }); + fs.writeFileSync(out(relPath), JSON.stringify(data, null, 2) + "\n"); +}; + +const git = (...gitArgs) => + execFileSync("git", gitArgs, { cwd: root, encoding: "utf8" }).trim(); +const version = `0.0.${git("rev-list", "--count", "HEAD")}`; +const gitHead = git("rev-parse", "HEAD"); + +for (const entry of fs.readdirSync(outDir)) { + if (!committedFiles.has(entry)) { + fs.rmSync(out(entry), { recursive: true, force: true }); + } +} + +fs.cpSync(abs("resources"), out("interpreting/resources"), { recursive: true }); +fs.cpSync(abs("decoding"), out("decoding"), { recursive: true }); +fs.copyFileSync(abs("LICENSE.md"), out("LICENSE.md")); + +for (const suite of suites) { + writeJSON(suiteFile(suite), { tests: suite.tests }); +} +const packageJson = JSON.parse( + fs.readFileSync(out("package.template.json"), "utf8") +); + +const decodingTypes = walk(abs("decoding")) + .filter(f => f.endsWith(".d.ts")) + .map(rel) + .sort(); +const exportedFiles = [ + "package.json", + "suites.json", + ...suites.map(suiteFile), + ...decodingTypes, +]; +writeJSON("package.json", { + ...packageJson, + version, + gitHead, + exports: { + ".": { + "types@>=7.1.0-0": "./types/index.d.ts", + types: "./types/types.d.ts", + }, + ...Object.fromEntries(exportedFiles.map(f => [`./${f}`, `./${f}`])), + }, +}); +writeJSON("suites.json", { + suites: suites.map(suite => ({ + kind: suite.kind, + file: suiteFile(suite), + })), +}); + +console.log(`Built ${packageJson.name}@${version} in ${rel(outDir)}/`); +for (const { name, kind, tests } of suites) { + console.log(` ${name} (${kind}): ${tests.length} tests`); +} diff --git a/test/fixtures/test426/scripts/check-types.js b/test/fixtures/test426/scripts/check-types.js new file mode 100644 index 000000000000..7c5cbf0d67e6 --- /dev/null +++ b/test/fixtures/test426/scripts/check-types.js @@ -0,0 +1,150 @@ +// Checks that the JSON files in the generated npm package match +// the TS definitions. Run build-npm-package.js first. + +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const root = path.resolve(import.meta.dirname, ".."); +const outDir = path.join(root, ".tmp", "check-types"); +const tsc = path.join(root, "node_modules", ".bin", "tsc"); + +const compilerOptions = { + strict: true, + noEmit: true, + skipLibCheck: false, + module: "nodenext", + moduleResolution: "nodenext", + target: "esnext", + types: [], +}; + +const relFrom = (from, to) => { + const rel = path.relative(path.dirname(from), to).split(path.sep).join("/"); + return rel.startsWith(".") ? rel : `./${rel}`; +}; + +function writeCheck(tsFile, header, type, jsonFile) { + const json = fs.readFileSync(jsonFile, "utf8"); + fs.mkdirSync(path.dirname(tsFile), { recursive: true }); + fs.writeFileSync( + tsFile, + `${header}\nexport default ${json.trim()} satisfies ${type};\n` + ); +} + +function runTsc(name, projectDir, files) { + const tsconfig = path.join(projectDir, "tsconfig.json"); + fs.writeFileSync(tsconfig, JSON.stringify({ compilerOptions, files }, null, 2)); + const { status } = spawnSync(tsc, ["-p", tsconfig, "--pretty"], { + stdio: "inherit", + }); + console.log(`${status === 0 ? "ok" : "FAIL"} ${name}`); + return status === 0; +} + +fs.rmSync(outDir, { recursive: true, force: true }); +let ok = true; + +// npm package files. + +const pkgDir = path.join(root, "npm-package"); +if (!fs.existsSync(path.join(pkgDir, "suites.json"))) { + console.error("npm-package/suites.json not found, run scripts/build-npm-package.js first."); + process.exit(1); +} +{ + const projectDir = path.join(outDir, "npm-package"); + const typesFile = path.join(pkgDir, "types", "types.d.ts"); + const files = []; + const check = (jsonRel, type) => { + const tsFile = path.join(projectDir, `${jsonRel}.ts`); + const header = `import type { ${type} } from "${relFrom(tsFile, typesFile)}";`; + writeCheck(tsFile, header, type, path.join(pkgDir, jsonRel)); + files.push(path.relative(projectDir, tsFile)); + }; + + const index = JSON.parse(fs.readFileSync(path.join(pkgDir, "suites.json"), "utf8")); + const expectedTypes = new Map([["suites.json", "SuitesIndex"]]); + for (const { kind, file } of index.suites) { + expectedTypes.set(file, kind === "interpreting" ? "InterpretingSuite" : "DecodingSuite"); + } + for (const [file, type] of expectedTypes) check(file, type); + + // Check that importing each exported JSON file `with { type: "json" }` gets + // its declared type from modules.d.ts. + const { name: pkgName, exports } = JSON.parse( + fs.readFileSync(path.join(pkgDir, "package.json"), "utf8") + ); + const exportedJSON = Object.keys(exports) + .filter(key => key.endsWith(".json") && key !== "./package.json") + .map(key => key.slice(2)); + const importsFile = path.join(projectDir, "imports.ts"); + const typeNames = [...new Set(expectedTypes.values())]; + fs.writeFileSync( + importsFile, + [ + `import type { ${typeNames.join(", ")} } from "${relFrom(importsFile, path.join(pkgDir, "types", "index.js"))}";`, + "type Equals = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false;", + ...exportedJSON.flatMap((file, i) => { + const type = expectedTypes.get(file); + if (!type) throw new Error(`Exported ${file} is not a known suite file`); + return [ + `import json${i} from "${pkgName}/${file}" with { type: "json" };`, + `export const check${i}: Equals = true; // ${file}`, + ]; + }), + ].join("\n") + "\n" + ); + files.push(path.relative(projectDir, importsFile)); + + ok = runTsc("npm-package", projectDir, files) && ok; + + // Check that each exported decoding/**/*.d.ts file can be loaded through the + // package's exports. Each one is checked separately, because they extend + // the same global interfaces. + for (const key of Object.keys(exports).filter(k => k.endsWith(".d.ts"))) { + const name = `npm-package ${key.slice(2)}`; + const dtsDir = path.join(outDir, "exports", key.slice(2)); + const linkDir = path.join(dtsDir, "node_modules", ...pkgName.split("/")); + fs.mkdirSync(path.dirname(linkDir), { recursive: true }); + fs.symlinkSync(pkgDir, linkDir, "dir"); + fs.writeFileSync( + path.join(dtsDir, "index.ts"), + `/// \nexport type T = SourceMapRecord;\n` + ); + ok = runTsc(name, dtsDir, ["index.ts"]) && ok; + } +} + +// Check golden files + +const decodingDir = path.join(root, "decoding"); +const decodingSuiteDirs = [ + decodingDir, + ...fs + .readdirSync(decodingDir, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => path.join(decodingDir, e.name)), +]; +for (const dir of decodingSuiteDirs) { + const goldens = fs.readdirSync(dir).filter(f => f.endsWith(".golden")).sort(); + if (goldens.length === 0) continue; + + const name = path.relative(root, dir).split(path.sep).join("/"); + const projectDir = path.join(outDir, name); + const suiteTypes = path.join(dir, `${path.basename(dir)}.d.ts`); + const typesFile = fs.existsSync(suiteTypes) + ? suiteTypes + : path.join(decodingDir, "source-map-record.d.ts"); + + const files = goldens.map(golden => { + const tsFile = path.join(projectDir, `${golden}.ts`); + const header = `/// `; + writeCheck(tsFile, header, "SourceMapRecord", path.join(dir, golden)); + return path.relative(projectDir, tsFile); + }); + ok = runTsc(name, projectDir, files) && ok; +} + +process.exit(ok ? 0 : 1); diff --git a/test/test426/README.md b/test/test426/README.md index 6935fc43c3d2..85db408434bc 100644 --- a/test/test426/README.md +++ b/test/test426/README.md @@ -7,7 +7,7 @@ suite ensures that the Node.js source map implementation conforms to the The [`test/fixtures/test426`](../fixtures/test426/) contains a copy of the set of [Source Map Tests][] suite. The last updated hash is: -* +* See the json files in [the `status` folder](./status) for prerequisites, expected failures, and support status for specific tests. From c617a93aa96418604b68d22a1369796a9dc60894 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 27 Oct 2026 22:56:15 +0100 Subject: [PATCH 004/195] build: remove unused FIPS export condition Refs: https://github.com/nodejs/node/pull/64777#pullrequestreview-5645113536 Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/67005 Reviewed-By: Richard Lau Reviewed-By: Antoine du Hamel --- node.gyp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/node.gyp b/node.gyp index 1141736d3c4a..714609b03e66 100644 --- a/node.gyp +++ b/node.gyp @@ -966,11 +966,6 @@ '-Bdeps/zlib/win32/zlib.def' ], }, - 'conditions': [ - ['openssl_is_fips=="true"', { - 'variables': { 'mkssldef_flags': ['-DOPENSSL_FIPS'] }, - }], - ], 'actions': [ { 'action_name': 'mkssldef', From 78227c9a3577e398452c33950535a4245e99fe05 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:28:19 -0700 Subject: [PATCH 005/195] ffi: free libuv error message on failed dlopen When uv_dlopen() fails, the error message it allocates is only released by uv_dlclose(). DynamicLibrary::Close() skips uv_dlclose() when the handle is null, so each failed ffi.dlopen() leaked the message. Call uv_dlclose() on the failure path, as DLib::Open() does. Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Assisted-by: claude:opus-5.5 PR-URL: https://github.com/nodejs/node/pull/67007 Fixes: https://github.com/nodejs/node/issues/67006 Reviewed-By: Daeyeon Jeong Reviewed-By: Paolo Insogna --- src/node_ffi.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node_ffi.cc b/src/node_ffi.cc index fe110eeae8ea..d6c50757f4f3 100644 --- a/src/node_ffi.cc +++ b/src/node_ffi.cc @@ -578,6 +578,8 @@ void DynamicLibrary::New(const FunctionCallbackInfo& args) { opened ? static_cast(lib->lib_.handle) : nullptr); if (!opened) { THROW_ERR_FFI_CALL_FAILED(env, "dlopen failed: %s", uv_dlerror(&lib->lib_)); + // Free the error message. Close() skips uv_dlclose() for a null handle. + uv_dlclose(&lib->lib_); return; } } From 1eb7ba85754d90c07a66f7db46610557f0803104 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Tue, 27 Oct 2026 18:46:44 -0700 Subject: [PATCH 006/195] module: cache compiled WebAssembly modules in the compile cache Extends the module compile cache to WebAssembly modules loaded through the ES module integration. A new kWasm entry type is keyed on the module URL with the wire bytes as the hashed source, and stores the serialized CompiledWasmModule. On load, cached code is deserialized through v8::WasmModuleCompilation with the same compile options the translator uses (js-string builtins and imported string constants), falling back to compilation when V8 rejects it. As for JavaScript, the cache entry is generated right after compilation. V8 only serializes optimized tier code, so with the default lazy baseline compilation there is nothing to cache and no entry is written; a complete cache requires --no-liftoff --no-wasm-lazy-compilation, which is documented as the way to use the compile cache for WebAssembly. Signed-off-by: Guy Bedford Assisted-by: Claude Code PR-URL: https://github.com/nodejs/node/pull/66191 Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- doc/api/module.md | 24 +++- lib/internal/modules/esm/translators.js | 36 +++-- src/compile_cache.cc | 50 ++++++- src/compile_cache.h | 10 +- src/node_modules.cc | 175 +++++++++++++++++++++++ test/parallel/test-compile-cache-wasm.js | 85 +++++++++++ 6 files changed, 362 insertions(+), 18 deletions(-) create mode 100644 test/parallel/test-compile-cache-wasm.js diff --git a/doc/api/module.md b/doc/api/module.md index cde48888700a..f86167e8d44f 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -371,8 +371,9 @@ changes: The module compile cache can be enabled either using the [`module.enableCompileCache()`][] method or the [`NODE_COMPILE_CACHE=dir`][] environment variable. After it is enabled, -whenever Node.js compiles a CommonJS, an ECMAScript Module, or a TypeScript module, it will -use on-disk [V8 code cache][] persisted in the specified directory to speed up the compilation. +whenever Node.js compiles a CommonJS, an ECMAScript Module, a TypeScript module, or a +WebAssembly module, it will use on-disk [V8 code cache][] persisted in the specified +directory to speed up the compilation. This may slow down the first load of a module graph, but subsequent loads of the same module graph may get a significant speedup if the contents of the modules do not change. @@ -433,6 +434,25 @@ There are two ways to enable the portable mode: 2. Setting the environment variable: [`NODE_COMPILE_CACHE_PORTABLE=1`][] +### WebAssembly modules in the compile cache + +For WebAssembly modules loaded through the ES module integration, the compile cache stores +the machine code that V8 has generated for the module when it is compiled. V8 only +serializes code produced by its optimizing compiler. By default WebAssembly functions are +compiled lazily with a baseline compiler and only optimized once they run hot, so at the time +a module is compiled there is typically no optimized code and no cache entry is written. + +To cache WebAssembly modules, compile all functions with the optimizing compiler when the +module is created: + +```console +$ NODE_COMPILE_CACHE=/path/to/cache node --no-liftoff --no-wasm-lazy-compilation app.js +``` + +This makes the first, uncached load of a module slower in exchange for later loads skipping +compilation entirely. Since V8 flags are part of the cache key, the same flags must be +used for all processes sharing the cache. + ### Read-only compile cache A cache that was generated ahead of time, for example at build time to be diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 8b90849143d8..583b6d1fe91d 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -13,6 +13,7 @@ const { StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeStartsWith, + Uint8Array, globalThis, } = primordials; @@ -47,6 +48,7 @@ const { populateCJSExportsFromESM, } = require('internal/modules/cjs/loader'); const { fileURLToPath, pathToFileURL, URL } = require('internal/url'); +const { isAnyArrayBuffer } = require('internal/util/types'); let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); @@ -63,6 +65,10 @@ const { ModuleWrap, kEvaluationPhase } = moduleWrap; const { getSourceSync } = require('internal/modules/esm/load'); const { parse: cjsParse } = internalBinding('cjs_lexer'); +const { + getWasmCompileCacheEntry, + saveWasmCompileCacheEntry, +} = internalBinding('modules'); const translators = new SafeMap(); exports.translators = translators; @@ -555,6 +561,12 @@ translators.set('json', function jsonStrategy(url, translateContext) { * >} [[Instance]] slot proxy for WebAssembly Module Record */ const wasmInstances = new SafeWeakMap(); +// Compile options per https://webassembly.github.io/esm-integration/js-api/index.html#parse-a-webassembly-module. +const wasmCompileOptions = { + __proto__: null, + builtins: ['js-string'], + importedStringConstants: 'wasm:js/string-constants', +}; translators.set('wasm', function(url, translateContext) { const { source } = translateContext; // WebAssembly global is not available during snapshot building, so we need to get it lazily. @@ -566,15 +578,21 @@ translators.set('wasm', function(url, translateContext) { debug(`Translating WASMModule ${url}`, translateContext); - let compiled; - try { - compiled = new WebAssembly.Module(source, { - builtins: ['js-string'], - importedStringConstants: 'wasm:js/string-constants', - }); - } catch (err) { - err.message = errPath(url) + ': ' + err.message; - throw err; + // When the compile cache is enabled, the cache entry holds the module + // deserialized from cached optimized code if V8 accepted it. + const cacheEntry = getWasmCompileCacheEntry( + isAnyArrayBuffer(source) ? new Uint8Array(source) : source, url, wasmCompileOptions); + let compiled = cacheEntry?.module; + if (compiled === undefined) { + try { + compiled = new WebAssembly.Module(source, wasmCompileOptions); + } catch (err) { + err.message = errPath(url) + ': ' + err.message; + throw err; + } + if (cacheEntry !== undefined) { + saveWasmCompileCacheEntry(cacheEntry.external, compiled); + } } const importsList = new SafeSet(); diff --git a/src/compile_cache.cc b/src/compile_cache.cc index 6e891619f42b..bab24256e06e 100644 --- a/src/compile_cache.cc +++ b/src/compile_cache.cc @@ -23,6 +23,7 @@ using v8::Local; using v8::Module; using v8::ScriptCompiler; using v8::String; +using v8::WasmModuleObject; namespace { std::string Uint32ToHex(uint32_t crc) { @@ -103,6 +104,8 @@ const char* CompileCacheEntry::type_name() const { return "ESM"; case CachedCodeType::kStrippedTypeScript: return "StrippedTypeScript"; + case CachedCodeType::kWasm: + return "Wasm"; default: UNREACHABLE(); } @@ -249,6 +252,20 @@ static std::string GetRelativePath(std::string_view path, CompileCacheEntry* CompileCacheHandler::GetOrInsert(Local code, Local filename, CachedCodeType type) { + // TODO(joyeecheung): don't encode this again into UTF8. If we read the + // UTF8 content on disk as raw buffer (from the JS layer, while watching out + // for monkey patching), we can just hash it directly. + Utf8Value code_utf8(isolate_, code); + return GetOrInsert(reinterpret_cast(code_utf8.out()), + code_utf8.length(), + filename, + type); +} + +CompileCacheEntry* CompileCacheHandler::GetOrInsert(const uint8_t* code, + size_t code_size, + Local filename, + CachedCodeType type) { DCHECK(!compile_cache_dir_.empty()); Environment* env = Environment::GetCurrent(isolate_->GetCurrentContext()); @@ -275,11 +292,7 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert(Local code, } uint32_t key = GetCacheKey(file_path, type); - // TODO(joyeecheung): don't encode this again into UTF8. If we read the - // UTF8 content on disk as raw buffer (from the JS layer, while watching out - // for monkey patching), we can just hash it directly. - Utf8Value code_utf8(isolate_, code); - uint32_t code_hash = GetHash(code_utf8.out(), code_utf8.length()); + uint32_t code_hash = GetHash(reinterpret_cast(code), code_size); auto loaded = compiler_cache_store_.find(key); // TODO(joyeecheung): let V8's in-isolate compilation cache take precedence. @@ -295,7 +308,7 @@ CompileCacheEntry* CompileCacheHandler::GetOrInsert(Local code, auto* result = emplaced.first->second.get(); result->code_hash = code_hash; - result->code_size = code_utf8.length(); + result->code_size = code_size; result->cache_key = key; result->cache_filename = compile_cache_dir_ + kPathSeparator + Uint32ToHex(key); @@ -318,6 +331,18 @@ ScriptCompiler::CachedData* SerializeCodeCache(Local mod) { return ScriptCompiler::CreateCodeCache(mod->GetUnboundModuleScript()); } +// V8 only serializes code compiled by the optimizing tier, and produces +// nothing if there is none, e.g. for a module that has only been compiled +// with the baseline tier so far. +ScriptCompiler::CachedData* SerializeCodeCache(Local mod) { + v8::OwnedBuffer code = mod->GetCompiledModule().Serialize(); + if (code.size == 0) return nullptr; + return new ScriptCompiler::CachedData( + code.buffer.release(), + static_cast(code.size), + ScriptCompiler::CachedData::BufferOwned); +} + template void CompileCacheHandler::MaybeSaveImpl(CompileCacheEntry* entry, Local func_or_mod, @@ -341,6 +366,12 @@ void CompileCacheHandler::MaybeSaveImpl(CompileCacheEntry* entry, entry->cache == nullptr ? "initializing" : "refreshing"); ScriptCompiler::CachedData* data = SerializeCodeCache(func_or_mod); + if (data == nullptr) { + Debug("[compile cache] nothing to serialize for %s %s\n", + entry->type_name(), + entry->source_filename); + return; + } DCHECK_EQ(data->buffer_policy, ScriptCompiler::CachedData::BufferOwned); entry->refreshed = true; entry->cache.reset(data); @@ -359,6 +390,13 @@ void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry, MaybeSaveImpl(entry, func, rejected); } +void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry, + Local mod, + bool rejected) { + DCHECK(entry->type == CachedCodeType::kWasm); + MaybeSaveImpl(entry, mod, rejected); +} + void CompileCacheHandler::MaybeSave(CompileCacheEntry* entry, std::string_view transpiled) { if (read_only_) { diff --git a/src/compile_cache.h b/src/compile_cache.h index 0fbce3624a61..527f72f33d42 100644 --- a/src/compile_cache.h +++ b/src/compile_cache.h @@ -16,7 +16,8 @@ class Environment; #define CACHED_CODE_TYPES(V) \ V(kCommonJS, 0) \ V(kESM, 1) \ - V(kStrippedTypeScript, 2) + V(kStrippedTypeScript, 2) \ + V(kWasm, 3) enum class CachedCodeType : uint8_t { #define V(type, value) type = value, @@ -89,12 +90,19 @@ class CompileCacheHandler { CompileCacheEntry* GetOrInsert(v8::Local code, v8::Local filename, CachedCodeType type); + CompileCacheEntry* GetOrInsert(const uint8_t* code, + size_t code_size, + v8::Local filename, + CachedCodeType type); void MaybeSave(CompileCacheEntry* entry, v8::Local func, bool rejected); void MaybeSave(CompileCacheEntry* entry, v8::Local mod, bool rejected); + void MaybeSave(CompileCacheEntry* entry, + v8::Local mod, + bool rejected); void MaybeSave(CompileCacheEntry* entry, std::string_view transpiled); std::string_view cache_dir() { return compile_cache_dir_; } bool read_only() const { return read_only_; } diff --git a/src/node_modules.cc b/src/node_modules.cc index f88fc461f4d6..414e3a780f7f 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -1,5 +1,8 @@ #include "node_modules.h" #include +#include +#include +#include #include "base_object-inl.h" #include "compile_cache.h" #include "node_errors.h" @@ -25,11 +28,14 @@ using v8::Boolean; using v8::Context; using v8::External; using v8::FunctionCallbackInfo; +using v8::Global; using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; using v8::LocalVector; +using v8::MaybeLocal; +using v8::MemorySpan; using v8::Name; using v8::NewStringType; using v8::Null; @@ -39,6 +45,9 @@ using v8::Primitive; using v8::String; using v8::Undefined; using v8::Value; +using v8::WasmModuleCompilation; +using v8::WasmModuleObject; +using v8::WasmStreaming; void BindingData::MemoryInfo(MemoryTracker* tracker) const { // Do nothing @@ -774,6 +783,166 @@ void SaveCompileCacheEntry(const FunctionCallbackInfo& args) { env->compile_cache_handler()->MaybeSave(cache_entry, utf8.ToStringView()); } +// Deserializes cached compiled code for the given wire bytes. Returns an empty +// MaybeLocal if V8 rejects the cached code, in which case the module must be +// compiled from the wire bytes as usual. +MaybeLocal DeserializeWasmModule( + Isolate* isolate, + MemorySpan wire, + MemorySpan code, + const WasmModuleObject::CompileOptions& options) { + // The resolution callback runs inside a HandleScope owned by V8, so the + // result must be persisted in a Global to outlive it. + struct State { + bool in_finish = true; + bool accepted = false; + Global module; + }; + auto state = std::make_shared(); + + WasmModuleCompilation compilation(options); + compilation.SetHasCompiledModuleBytes(); + compilation.OnBytesReceived(wire.data(), wire.size()); + compilation.Finish( + isolate, + [&](WasmStreaming::ModuleCachingInterface& cache) { + state->accepted = cache.SetCachedCompiledModuleBytes(code); + }, + [state, + isolate](std::variant, Local> result) { + // When the cached code is accepted, V8 resolves synchronously within + // Finish(). Otherwise V8 falls back to compiling the wire bytes + // asynchronously; that result arrives later and is unused. + if (!state->in_finish) return; + if (auto* module = std::get_if>(&result)) { + state->module.Reset(isolate, *module); + } + }); + state->in_finish = false; + if (!state->accepted || state->module.IsEmpty()) return {}; + return state->module.Get(isolate); +} + +// Reads `{ builtins, importedStringConstants }` as accepted by the +// WebAssembly.Module constructor. `constants_module` provides storage for the +// string referenced by the returned options. +bool ParseWasmCompileOptions(Environment* env, + Local options, + std::string* constants_module, + WasmModuleObject::CompileOptions* result) { + Isolate* isolate = env->isolate(); + Local context = env->context(); + Local builtins; + if (!options->Get(context, FIXED_ONE_BYTE_STRING(isolate, "builtins")) + .ToLocal(&builtins)) { + return false; + } + if (builtins->IsArray()) { + Local array = builtins.As(); + for (uint32_t i = 0; i < array->Length(); i++) { + Local builtin; + if (!array->Get(context, i).ToLocal(&builtin)) return false; + if (builtin->IsString() && + builtin.As()->StringEquals( + FIXED_ONE_BYTE_STRING(isolate, "js-string"))) { + result->builtins |= + WasmModuleObject::CompileOptions::Builtins::kJsString; + } + } + } + Local constants; + if (!options + ->Get(context, + FIXED_ONE_BYTE_STRING(isolate, "importedStringConstants")) + .ToLocal(&constants)) { + return false; + } + if (constants->IsString()) { + *constants_module = Utf8Value(isolate, constants).ToString(); + result->imported_string_constants_module = constants_module->c_str(); + } + return true; +} + +// Returns undefined when the compile cache is disabled or not applicable, +// otherwise an object with an `external` handle to the cache entry, and, when +// the cached compiled code was accepted by V8, the deserialized `module`. +void GetWasmCompileCacheEntry(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + CHECK(args[0]->IsArrayBufferView()); + CHECK(args[1]->IsString()); + CHECK(args[2]->IsObject()); + Local context = isolate->GetCurrentContext(); + Environment* env = Environment::GetCurrent(context); + if (!env->use_compile_cache()) { + return; + } + ArrayBufferViewContents source(args[0]); + Local filename = args[1].As(); + auto* cache_entry = env->compile_cache_handler()->GetOrInsert( + source.data(), source.length(), filename, CachedCodeType::kWasm); + if (cache_entry == nullptr) { + return; + } + + LocalVector names(isolate, + {FIXED_ONE_BYTE_STRING(isolate, "external")}); + LocalVector values( + isolate, + {External::New( + isolate, cache_entry, v8::kExternalPointerTypeTagDefault)}); + if (cache_entry->cache != nullptr) { + std::string constants_module; + WasmModuleObject::CompileOptions options; + if (!ParseWasmCompileOptions( + env, args[2].As(), &constants_module, &options)) { + return; + } + Debug(env, + DebugCategory::COMPILE_CACHE, + "[compile cache] deserializing %s %s...", + cache_entry->type_name(), + cache_entry->source_filename); + Local module; + if (DeserializeWasmModule(isolate, + {source.data(), source.length()}, + {cache_entry->cache->data, + static_cast(cache_entry->cache->length)}, + options) + .ToLocal(&module)) { + Debug(env, DebugCategory::COMPILE_CACHE, "success\n"); + names.push_back(FIXED_ONE_BYTE_STRING(isolate, "module")); + values.push_back(module); + } else { + Debug(env, DebugCategory::COMPILE_CACHE, "rejected\n"); + } + } else { + Debug(env, + DebugCategory::COMPILE_CACHE, + "[compile cache] no cache for %s %s\n", + cache_entry->type_name(), + cache_entry->source_filename); + } + args.GetReturnValue().Set(Object::New( + isolate, v8::Null(isolate), names.data(), values.data(), names.size())); +} + +void SaveWasmCompileCacheEntry(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + Environment* env = Environment::GetCurrent(context); + DCHECK(env->use_compile_cache()); + CHECK(args[0]->IsExternal()); + CHECK(args[1]->IsWasmModuleObject()); + auto* cache_entry = static_cast( + args[0].As()->Value(v8::kExternalPointerTypeTagDefault)); + // Only called when the module was compiled from the wire bytes, so any + // existing cache was rejected. + env->compile_cache_handler()->MaybeSave(cache_entry, + args[1].As(), + cache_entry->cache != nullptr); +} + void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); @@ -796,6 +965,10 @@ void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "flushCompileCache", FlushCompileCache); SetMethod(isolate, target, "getCompileCacheEntry", GetCompileCacheEntry); SetMethod(isolate, target, "saveCompileCacheEntry", SaveCompileCacheEntry); + SetMethod( + isolate, target, "getWasmCompileCacheEntry", GetWasmCompileCacheEntry); + SetMethod( + isolate, target, "saveWasmCompileCacheEntry", SaveWasmCompileCacheEntry); } void BindingData::CreatePerContextProperties(Local target, @@ -869,6 +1042,8 @@ void BindingData::RegisterExternalReferences( registry->Register(FlushCompileCache); registry->Register(GetCompileCacheEntry); registry->Register(SaveCompileCacheEntry); + registry->Register(GetWasmCompileCacheEntry); + registry->Register(SaveWasmCompileCacheEntry); } } // namespace modules diff --git a/test/parallel/test-compile-cache-wasm.js b/test/parallel/test-compile-cache-wasm.js new file mode 100644 index 000000000000..278198378dd2 --- /dev/null +++ b/test/parallel/test-compile-cache-wasm.js @@ -0,0 +1,85 @@ +'use strict'; + +// This tests NODE_COMPILE_CACHE works for WebAssembly modules imported as ESM. + +require('../common'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const assert = require('assert'); +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); + +const script = fixtures.path('es-modules', 'test-wasm-js-string-builtins.mjs'); + +// V8 only serializes optimized code, so a complete cache requires eager +// optimizing compilation. +{ + tmpdir.refresh(); + const dir = tmpdir.resolve('.compile_cache_dir'); + const flags = ['--no-liftoff', '--no-wasm-lazy-compilation', '--no-warnings']; + + spawnSyncAndAssert( + process.execPath, + [...flags, script], + { + env: { + ...process.env, + NODE_DEBUG_NATIVE: 'COMPILE_CACHE', + NODE_COMPILE_CACHE: dir, + }, + cwd: tmpdir.path, + }, + { + stderr(output) { + assert.match(output, /no cache for Wasm .*js-string-builtins\.wasm/); + assert.match(output, /V8 code cache for Wasm .*js-string-builtins\.wasm was not initialized, initializing the in-memory entry/); + assert.match(output, /writing cache for Wasm .*js-string-builtins\.wasm.*success/); + return true; + }, + }); + + spawnSyncAndAssert( + process.execPath, + [...flags, script], + { + env: { + ...process.env, + NODE_DEBUG_NATIVE: 'COMPILE_CACHE', + NODE_COMPILE_CACHE: dir, + }, + cwd: tmpdir.path, + }, + { + stderr(output) { + assert.match(output, /reading cache from .* for Wasm .*js-string-builtins\.wasm.*success/); + assert.match(output, /deserializing Wasm .*js-string-builtins\.wasm.*success/); + assert.match(output, /skip persisting Wasm .*js-string-builtins\.wasm because cache was the same/); + return true; + }, + }); +} + +// With the default lazy baseline compilation there is no optimized code to +// cache at compile time, and no cache entry is written. +{ + tmpdir.refresh(); + const dir = tmpdir.resolve('.compile_cache_dir'); + + spawnSyncAndAssert( + process.execPath, + ['--no-warnings', script], + { + env: { + ...process.env, + NODE_DEBUG_NATIVE: 'COMPILE_CACHE', + NODE_COMPILE_CACHE: dir, + }, + cwd: tmpdir.path, + }, + { + stderr(output) { + assert.match(output, /nothing to serialize for Wasm .*js-string-builtins\.wasm/); + assert.match(output, /skip persisting Wasm .*js-string-builtins\.wasm because the cache was not initialized/); + return true; + }, + }); +} From 5409ed48c90f2a5bef72be54b185c990ff2601af Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 30 Aug 2026 09:04:52 +0200 Subject: [PATCH 007/195] crypto,tls: clone X.509 chains across isolates X509Certificate cloning drops issuerCertificate and shares its native X509 object with the receiving isolate. Snapshot the leaf and supplied peer chain as DER, then parse separate native certificates in each isolate. Construct the JavaScript chain iteratively for TLS inspection, structuredClone(), MessagePort, and workers. Each supplied position gets its own object, including repeated certificate encodings. Refs: https://github.com/nodejs/node/pull/65602 Assisted-by: Codex Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/66898 Reviewed-By: Tim Perry --- src/crypto/crypto_x509.cc | 272 ++++++++++++++---- src/crypto/crypto_x509.h | 83 ++++-- .../test-crypto-x509-chain-snapshot.js | 95 ++++++ test/parallel/test-crypto-x509-peer-chain.js | 110 +++++++ 4 files changed, 480 insertions(+), 80 deletions(-) create mode 100644 test/parallel/test-crypto-x509-chain-snapshot.js create mode 100644 test/parallel/test-crypto-x509-peer-chain.js diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index c7dbe956149b..3c208082f944 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -55,17 +56,94 @@ using v8::Value; namespace crypto { -ManagedX509::ManagedX509(X509Pointer&& cert) : cert_(std::move(cert)) {} +namespace { + +std::optional EncodeCertificate( + const X509View& cert) { + MarkPopErrorOnReturn mark_pop_error_on_return; + if (!cert) return std::nullopt; + + const int size = i2d_X509(cert.get(), nullptr); + if (size <= 0) return std::nullopt; + + X509CertificateSnapshot::DER der(static_cast(size)); + unsigned char* cursor = der.data(); + if (i2d_X509(cert.get(), &cursor) != size || + cursor != der.data() + der.size()) { + return std::nullopt; + } + return der; +} + +X509Pointer ParseCertificate(const X509CertificateSnapshot::DER& certificate) { + MarkPopErrorOnReturn mark_pop_error_on_return; + constexpr size_t kMaximumCertificateSize = static_cast( + std::numeric_limits::max()); // NOLINT(runtime/int) + if (certificate.empty() || certificate.size() > kMaximumCertificateSize) { + return {}; + } -ManagedX509::ManagedX509(const ManagedX509& that) { - *this = that; + const unsigned char* cursor = certificate.data(); + const unsigned char* const end = cursor + certificate.size(); + X509Pointer cert( + d2i_X509(nullptr, + &cursor, + static_cast(certificate.size()))); // NOLINT(runtime/int) + if (!cert || cursor != end) return {}; + return cert; } -ManagedX509& ManagedX509::operator=(const ManagedX509& that) { - cert_.reset(that.get()); - if (cert_) [[likely]] - X509_up_ref(cert_.get()); - return *this; +} // namespace + +ManagedX509::ManagedX509(X509Pointer cert) : cert_(std::move(cert)) {} + +std::shared_ptr ManagedX509::Adopt(X509Pointer cert) { + if (!cert) return {}; + return std::shared_ptr(new ManagedX509(std::move(cert))); +} + +X509CertificateSnapshot::X509CertificateSnapshot(std::vector certificates) + : certificates_(std::move(certificates)) {} + +std::shared_ptr X509CertificateSnapshot::Create( + X509View leaf, + const STACK_OF(X509) * issuer_chain, + OPENSSL_SIZE_T issuers_start) { + auto leaf_der = EncodeCertificate(leaf); + if (!leaf_der.has_value()) return {}; + + const OPENSSL_SIZE_T count = + issuer_chain == nullptr ? 0 : sk_X509_num(issuer_chain); + std::vector certificates; + certificates.reserve(static_cast(count) + 1); + certificates.push_back(std::move(leaf_der.value())); + for (OPENSSL_SIZE_T i = issuers_start; i < count; i++) { + const X509View candidate(sk_X509_value(issuer_chain, i)); + auto candidate_der = EncodeCertificate(candidate); + if (!candidate_der.has_value()) return {}; + certificates.push_back(std::move(candidate_der.value())); + } + + return std::shared_ptr( + new X509CertificateSnapshot(std::move(certificates))); +} + +X509Pointer X509CertificateSnapshot::Parse(size_t index) const { + if (index >= certificates_.size()) return {}; + return ParseCertificate(certificates_[index]); +} + +size_t X509CertificateSnapshot::size_bytes() const { + return std::accumulate(certificates_.begin(), + certificates_.end(), + size_t{0}, + [](size_t total, const DER& certificate) { + return total + certificate.size(); + }); +} + +void X509CertificateSnapshot::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("certificates", size_bytes()); } void ManagedX509::MemoryInfo(MemoryTracker* tracker) const { @@ -947,9 +1025,25 @@ void X509Certificate::NewFromHandle(const FunctionCallbackInfo& args) { if (!result.value) [[unlikely]] { return ThrowCryptoError(env, result.error.value_or(0)); } + + auto snapshot = X509CertificateSnapshot::Create(result.value.view()); + if (!snapshot) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to snapshot X.509 certificate"); + return; + } + + auto parsed = snapshot->Parse(0); + if (!parsed) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED( + env, "Failed to parse X.509 certificate snapshot"); + return; + } new X509Certificate(env, args.This(), - std::make_shared(std::move(result.value))); + ManagedX509::Adopt(std::move(parsed)), + std::move(snapshot), + 0); return; } @@ -963,7 +1057,12 @@ void X509Certificate::NewFromHandle(const FunctionCallbackInfo& args) { CHECK_NOT_NULL(handle); Local issuer; if (handle->issuer_cert_) issuer = handle->issuer_cert_->object(); - new X509Certificate(env, args.This(), handle->cert_, issuer); + new X509Certificate(env, + args.This(), + handle->cert_, + handle->snapshot_, + handle->snapshot_index_, + issuer); } void X509Certificate::CreateX509CertificateClass( @@ -1002,50 +1101,95 @@ void X509Certificate::IsX509Certificate( MaybeLocal X509Certificate::New(Environment* env, X509Pointer cert, - const STACK_OF(X509) * issuer_chain) { - std::shared_ptr mcert(new ManagedX509(std::move(cert))); - return New(env, std::move(mcert), issuer_chain); -} + const STACK_OF(X509) * issuer_chain, + OPENSSL_SIZE_T issuers_start) { + if (!cert) return {}; + + auto snapshot = + X509CertificateSnapshot::Create(cert.view(), issuer_chain, issuers_start); + if (!snapshot) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED(env, + "Failed to snapshot X.509 certificate"); + return {}; + } -MaybeLocal X509Certificate::New(Environment* env, - std::shared_ptr cert, - const STACK_OF(X509) * issuer_chain) { - return NewWithIssuers(env, std::move(cert), issuer_chain, 0); + // Keep the live certificate independent from the backend object that was + // serialized while creating the immutable snapshot. Some backends populate + // or invalidate lazy caches while encoding malformed-but-parseable input. + auto parsed = snapshot->Parse(0); + if (!parsed) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED( + env, "Failed to parse X.509 certificate snapshot"); + return {}; + } + return New(env, ManagedX509::Adopt(std::move(parsed)), std::move(snapshot)); } -MaybeLocal X509Certificate::NewWithIssuers( +MaybeLocal X509Certificate::New( Environment* env, std::shared_ptr cert, - const STACK_OF(X509) * issuer_chain, - int start) { + std::shared_ptr snapshot, + size_t snapshot_index) { + CHECK(cert); + CHECK(snapshot); + CHECK_LT(snapshot_index, snapshot->size()); + EscapableHandleScope scope(env->isolate()); + Local context = env->context(); + Local issuer; + + for (size_t i = snapshot->size(); i > snapshot_index + 1; i--) { + const size_t index = i - 1; + auto parsed = snapshot->Parse(index); + if (!parsed) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED( + env, "Failed to parse X.509 certificate snapshot"); + return {}; + } + + Local issuer_object; + if (!GetConstructorTemplate(env) + ->InstanceTemplate() + ->NewInstance(context) + .ToLocal(&issuer_object)) { + return {}; + } + new X509Certificate(env, + issuer_object, + ManagedX509::Adopt(std::move(parsed)), + snapshot, + index, + issuer); + issuer = issuer_object; + } + Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() - ->NewInstance(env->context()) + ->NewInstance(context) .ToLocal(&obj)) { - return MaybeLocal(); + return {}; } + new X509Certificate( + env, obj, std::move(cert), std::move(snapshot), snapshot_index, issuer); + return scope.Escape(obj); +} - Local issuer; - if (issuer_chain != nullptr && - static_cast(start) < sk_X509_num(issuer_chain)) { - X509Pointer issuer_cert = - X509View(sk_X509_value(issuer_chain, start)).clone(); - if (!issuer_cert) [[unlikely]] { - return MaybeLocal(); - } - if (!NewWithIssuers(env, - std::make_shared(std::move(issuer_cert)), - issuer_chain, - start + 1) - .ToLocal(&issuer)) [[unlikely]] { - return MaybeLocal(); - } +MaybeLocal X509Certificate::New( + Environment* env, + std::shared_ptr snapshot, + size_t snapshot_index) { + if (!snapshot || snapshot_index >= snapshot->size()) return {}; + auto cert = snapshot->Parse(snapshot_index); + if (!cert) [[unlikely]] { + THROW_ERR_CRYPTO_OPERATION_FAILED( + env, "Failed to parse X.509 certificate snapshot"); + return {}; } - - new X509Certificate(env, obj, std::move(cert), issuer); - return scope.Escape(obj); + return New(env, + ManagedX509::Adopt(std::move(cert)), + std::move(snapshot), + snapshot_index); } MaybeLocal X509Certificate::GetCert(Environment* env, @@ -1064,26 +1208,25 @@ MaybeLocal X509Certificate::GetPeerCert(Environment* env, // The peer chain is owned by the SSL session and must not be modified. Its // first entry is the peer certificate on the client but not on the server. const STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); - int issuers_start = 0; + OPENSSL_SIZE_T issuers_start = 0; X509Pointer cert; if ((flag & GetPeerCertificateFlag::SERVER) == GetPeerCertificateFlag::SERVER) { cert = X509Pointer::PeerFrom(ssl); } + + if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) + return MaybeLocal(); + if (!cert) { - if (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0) - return MaybeLocal(); - cert = X509View(sk_X509_value(ssl_certs, 0)).clone(); - if (!cert) [[unlikely]] - return MaybeLocal(); + const X509* leaf = sk_X509_value(ssl_certs, 0); + if (leaf != nullptr) cert = X509View(leaf).clone(); issuers_start = 1; } - return NewWithIssuers(env, - std::make_shared(std::move(cert)), - ssl_certs, - issuers_start); + if (!cert) return {}; + return New(env, std::move(cert), ssl_certs, issuers_start); } v8::MaybeLocal X509Certificate::toObject(Environment* env) { @@ -1097,11 +1240,17 @@ v8::MaybeLocal X509Certificate::toObject(Environment* env, return X509ToObject(env, cert).FromMaybe(Local()); } -X509Certificate::X509Certificate(Environment* env, - Local object, - std::shared_ptr cert, - Local issuer_chain) - : BaseObject(env, object), cert_(std::move(cert)) { +X509Certificate::X509Certificate( + Environment* env, + Local object, + std::shared_ptr cert, + std::shared_ptr snapshot, + size_t snapshot_index, + Local issuer_chain) + : BaseObject(env, object), + cert_(std::move(cert)), + snapshot_(std::move(snapshot)), + snapshot_index_(snapshot_index) { MakeWeak(); if (!issuer_chain.IsEmpty()) { @@ -1111,6 +1260,12 @@ X509Certificate::X509Certificate(Environment* env, void X509Certificate::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("cert", cert_); + tracker->TrackField("snapshot", snapshot_); +} + +void X509Certificate::X509CertificateTransferData::MemoryInfo( + MemoryTracker* tracker) const { + tracker->TrackField("snapshot", snapshot_); } BaseObjectPtr @@ -1124,7 +1279,7 @@ X509Certificate::X509CertificateTransferData::Deserialize( } Local handle; - if (!X509Certificate::New(env, data_).ToLocal(&handle)) + if (!X509Certificate::New(env, snapshot_, snapshot_index_).ToLocal(&handle)) return {}; Local module = @@ -1157,7 +1312,8 @@ BaseObject::TransferMode X509Certificate::GetTransferMode() const { std::unique_ptr X509Certificate::CloneForMessaging() const { - return std::make_unique(cert_); + return std::make_unique(snapshot_, + snapshot_index_); } void X509Certificate::Initialize(Environment* env, Local target) { diff --git a/src/crypto/crypto_x509.h b/src/crypto/crypto_x509.h index a6f64d4f2d4f..44714d1bcca2 100644 --- a/src/crypto/crypto_x509.h +++ b/src/crypto/crypto_x509.h @@ -11,22 +11,24 @@ #include "node_worker.h" #include "v8.h" +#include +#include +#include + namespace node { namespace crypto { -// The ManagedX509 class is essentially a smart pointer for -// X509 objects that allows an X509Certificate instance to -// be cloned at the JS level while pointing at the same -// underlying X509 instance. +// ManagedX509 owns one isolate-local X509. Adopt makes the ownership transfer +// explicit at each call site. class ManagedX509 final : public MemoryRetainer { public: - ManagedX509() = default; - explicit ManagedX509(ncrypto::X509Pointer&& cert); - ManagedX509(const ManagedX509& that); - ManagedX509& operator=(const ManagedX509& that); + static std::shared_ptr Adopt(ncrypto::X509Pointer cert); + + ManagedX509(const ManagedX509&) = delete; + ManagedX509& operator=(const ManagedX509&) = delete; inline operator bool() const { return !!cert_; } - inline X509* get() const { return cert_.get(); } + inline X509* get() { return cert_.get(); } inline ncrypto::X509View view() const { return cert_; } inline operator ncrypto::X509View() const { return cert_; } @@ -35,9 +37,40 @@ class ManagedX509 final : public MemoryRetainer { SET_SELF_SIZE(ManagedX509) private: + explicit ManagedX509(ncrypto::X509Pointer cert); + ncrypto::X509Pointer cert_; }; +// An immutable certificate-encoding snapshot. Certificate zero is the leaf and +// all remaining certificates follow in the supplied order. Sharing this +// snapshot between isolates is safe; the isolate-local X509 objects are always +// reparsed from it. +class X509CertificateSnapshot final : public MemoryRetainer { + public: + using DER = std::vector; + + static std::shared_ptr Create( + ncrypto::X509View leaf, + const STACK_OF(X509) * issuer_chain = nullptr, + OPENSSL_SIZE_T issuers_start = 0); + + ncrypto::X509Pointer Parse(size_t index) const; + + inline size_t size() const { return certificates_.size(); } + inline const DER& at(size_t index) const { return certificates_.at(index); } + size_t size_bytes() const; + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(X509CertificateSnapshot) + SET_SELF_SIZE(X509CertificateSnapshot) + + private: + explicit X509CertificateSnapshot(std::vector certificates); + + const std::vector certificates_; +}; + class X509Certificate final : public BaseObject { public: enum class GetPeerCertificateFlag { @@ -60,12 +93,19 @@ class X509Certificate final : public BaseObject { static v8::MaybeLocal New( Environment* env, ncrypto::X509Pointer cert, - const STACK_OF(X509) * issuer_chain = nullptr); + const STACK_OF(X509) * issuer_chain = nullptr, + OPENSSL_SIZE_T issuers_start = 0); static v8::MaybeLocal New( Environment* env, std::shared_ptr cert, - const STACK_OF(X509) * issuer_chain = nullptr); + std::shared_ptr snapshot, + size_t snapshot_index = 0); + + static v8::MaybeLocal New( + Environment* env, + std::shared_ptr snapshot, + size_t snapshot_index = 0); static v8::MaybeLocal GetCert(Environment* env, const ncrypto::SSLPointer& ssl); @@ -96,20 +136,22 @@ class X509Certificate final : public BaseObject { class X509CertificateTransferData : public worker::TransferData { public: explicit X509CertificateTransferData( - const std::shared_ptr& data) - : data_(data) {} + std::shared_ptr snapshot, + size_t snapshot_index) + : snapshot_(std::move(snapshot)), snapshot_index_(snapshot_index) {} BaseObjectPtr Deserialize( Environment* env, v8::Local context, std::unique_ptr self) override; + void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(X509CertificateTransferData) SET_SELF_SIZE(X509CertificateTransferData) - SET_NO_MEMORY_INFO() private: - std::shared_ptr data_; + std::shared_ptr snapshot_; + size_t snapshot_index_; }; BaseObject::TransferMode GetTransferMode() const override; @@ -119,16 +161,13 @@ class X509Certificate final : public BaseObject { X509Certificate(Environment* env, v8::Local object, std::shared_ptr cert, + std::shared_ptr snapshot, + size_t snapshot_index, v8::Local issuer_chain = v8::Local()); - // Like New(), but reads the issuer chain from issuer_chain[start] upward. - static v8::MaybeLocal NewWithIssuers( - Environment* env, - std::shared_ptr cert, - const STACK_OF(X509) * issuer_chain, - int start); - std::shared_ptr cert_; + std::shared_ptr snapshot_; + size_t snapshot_index_ = 0; BaseObjectPtr issuer_cert_; }; diff --git a/test/parallel/test-crypto-x509-chain-snapshot.js b/test/parallel/test-crypto-x509-chain-snapshot.js new file mode 100644 index 000000000000..5a0f696da0f1 --- /dev/null +++ b/test/parallel/test-crypto-x509-chain-snapshot.js @@ -0,0 +1,95 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('node:assert'); +const { X509Certificate } = require('node:crypto'); +const { once } = require('node:events'); +const tls = require('node:tls'); +const { + MessageChannel, + Worker, +} = require('node:worker_threads'); +const fixtures = require('../common/fixtures'); + +const leaf = new X509Certificate(fixtures.readKey('agent6-cert.pem')); +const issuer = new X509Certificate(fixtures.readKey('ca3-cert.pem')); + +function assertChain(actual) { + assert(actual instanceof X509Certificate); + assert.deepStrictEqual(actual.raw, leaf.raw); + assert.strictEqual(actual.issuerCertificate, actual.issuerCertificate); + assert(actual.issuerCertificate instanceof X509Certificate); + assert.deepStrictEqual(actual.issuerCertificate.raw, issuer.raw); + assert.strictEqual(actual.issuerCertificate.issuerCertificate, undefined); +} + +function legacyChainRaw(certificate) { + const result = []; + const seen = new Set(); + while (certificate !== undefined && !seen.has(certificate)) { + seen.add(certificate); + result.push(certificate.raw); + certificate = certificate.issuerCertificate; + } + return result; +} + +async function cloneViaMessagePort(certificate) { + const { port1, port2 } = new MessageChannel(); + port1.postMessage(certificate); + const [clone] = await once(port2, 'message'); + port1.close(); + port2.close(); + return clone; +} + +async function cloneViaWorker(certificate) { + const worker = new Worker(` + 'use strict'; + const { parentPort } = require('node:worker_threads'); + parentPort.once('message', (certificate) => { + parentPort.postMessage(certificate); + }); + `, { eval: true }); + worker.postMessage(certificate); + const [clone] = await once(worker, 'message'); + await worker.terminate(); + return clone; +} + +const server = tls.createServer({ + key: fixtures.readKey('agent6-key.pem'), + cert: fixtures.readKey('agent6-cert.pem'), +}, (socket) => socket.end()); + +(async () => { + server.listen(0); + await once(server, 'listening'); + + const socket = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + }); + await once(socket, 'secureConnect'); + + const first = socket.getPeerX509Certificate(); + assertChain(first); + + const firstLegacy = socket.getPeerCertificate(true); + const second = socket.getPeerX509Certificate(); + const secondLegacy = socket.getPeerCertificate(true); + assertChain(second); + assert.deepStrictEqual(legacyChainRaw(firstLegacy), [leaf.raw, issuer.raw]); + assert.deepStrictEqual(legacyChainRaw(secondLegacy), [leaf.raw, issuer.raw]); + + assertChain(structuredClone(first)); + assertChain(await cloneViaMessagePort(first)); + assertChain(await cloneViaWorker(first)); + + socket.destroy(); + server.close(); + await once(server, 'close'); +})().then(common.mustCall()); diff --git a/test/parallel/test-crypto-x509-peer-chain.js b/test/parallel/test-crypto-x509-peer-chain.js new file mode 100644 index 000000000000..b60f3d00acc0 --- /dev/null +++ b/test/parallel/test-crypto-x509-peer-chain.js @@ -0,0 +1,110 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('node:assert'); +const { X509Certificate } = require('node:crypto'); +const { once } = require('node:events'); +const tls = require('node:tls'); +const { MessageChannel, Worker } = require('node:worker_threads'); +const fixtures = require('../common/fixtures'); + +const leaf = new X509Certificate(fixtures.readKey('agent6-cert.pem')); +const issuer = new X509Certificate(fixtures.readKey('ca3-cert.pem')); +const unrelated = new X509Certificate(fixtures.readKey('agent1-cert.pem')); +const key = fixtures.readKey('agent6-key.pem'); +const chains = [ + [leaf, issuer, unrelated], + [leaf, unrelated, issuer], + [leaf, issuer, issuer], + [leaf, issuer, unrelated, issuer], + [leaf, leaf, issuer], +]; + +function assertChain(actual, expected) { + const seen = new Set(); + for (const certificate of expected) { + assert(actual instanceof X509Certificate); + assert(!seen.has(actual)); + seen.add(actual); + assert.deepStrictEqual(actual.raw, certificate.raw); + assert.strictEqual(actual.issuerCertificate, actual.issuerCertificate); + actual = actual.issuerCertificate; + } + assert.strictEqual(actual, undefined); +} + +async function assertClones(certificate, chain) { + assertChain(structuredClone(certificate), chain); + + const { port1, port2 } = new MessageChannel(); + port1.postMessage(certificate); + const [messageClone] = await once(port2, 'message'); + port1.close(); + port2.close(); + assertChain(messageClone, chain); + + const worker = new Worker(` + const { parentPort } = require('node:worker_threads'); + parentPort.once('message', (certificate) => { + parentPort.postMessage(certificate); + }); + `, { eval: true }); + worker.postMessage(certificate); + const [workerClone] = await once(worker, 'message'); + await worker.terminate(); + assertChain(workerClone, chain); +} + +async function checkPeer(socket, chain) { + const first = socket.getPeerX509Certificate(); + assertChain(first, chain); + for (let i = 0; i < 2; i++) { + assert.deepStrictEqual(socket.getPeerCertificate(true).raw, leaf.raw); + assertChain(socket.getPeerX509Certificate(), chain); + } + await assertClones(first, chain); +} + +async function test(chain) { + // Encode each certificate separately: agent6-cert.pem contains its issuer. + const cert = chain.map((certificate) => certificate.toString()).join(''); + const server = tls.createServer({ + key, + cert, + requestCert: true, + rejectUnauthorized: false, + }); + server.listen(0); + await once(server, 'listening'); + + const peer = once(server, 'secureConnection'); + const client = tls.connect({ + port: server.address().port, + key, + cert, + rejectUnauthorized: false, + }); + await once(client, 'secureConnect'); + const [socket] = await peer; + + // Client and server certificate stacks handle the leaf differently; both + // sockets must expose every certificate sent by the peer exactly once. + await checkPeer(client, chain); + await checkPeer(socket, chain); + + // Clones must remain usable after their TLS session has been destroyed. + const clone = structuredClone(client.getPeerX509Certificate()); + client.destroy(); + socket.destroy(); + const closed = once(server, 'close'); + server.close(); + await closed; + assertChain(clone, chain); +} + +(async () => { + for (const chain of chains) await test(chain); +})().then(common.mustCall()); From 1d297add9e690c3317ab0092785c38cb48d35f7f Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Tue, 27 Oct 2026 13:35:30 +0100 Subject: [PATCH 008/195] crypto: preserve errors during X.509 issuer checks X509View::isIssuedBy() clears the caller's OpenSSL error queue on entry and exit, including when either certificate is missing. Use a mark/pop scope to discard errors from the issuer check without losing earlier diagnostics. Cover successful checks, mismatches, malformed extensions, and null inputs with empty and populated queues. Assisted-by: Codex Signed-off-by: Filip Skokan PR-URL: https://github.com/nodejs/node/pull/66898 Reviewed-By: Tim Perry --- deps/ncrypto/ncrypto.cc | 2 +- test/cctest/test_node_crypto.cc | 99 +++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 5c20b4828786..a28b9cd209e9 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1595,7 +1595,7 @@ bool X509View::isCA() const { } bool X509View::isIssuedBy(const X509View& issuer) const { - ClearErrorOnReturn clearErrorOnReturn; + MarkPopErrorOnReturn mark_pop_error_on_return; if (cert_ == nullptr || issuer.cert_ == nullptr) return false; return X509_check_issued(const_cast(issuer.cert_), const_cast(cert_)) == X509_V_OK; diff --git a/test/cctest/test_node_crypto.cc b/test/cctest/test_node_crypto.cc index 7658e1810cd4..45d5b7facc39 100644 --- a/test/cctest/test_node_crypto.cc +++ b/test/cctest/test_node_crypto.cc @@ -1025,6 +1025,105 @@ TEST(NodeCrypto, X509FormattingPreservesUnrelatedErrors) { ERR_clear_error(); } +TEST(NodeCrypto, X509IssuerChecksPreserveUnrelatedErrors) { + ncrypto::ClearErrorOnReturn clear_errors; + auto ctx = EVPKeyCtxPointer::NewFromAlgorithm(KeyAlgorithm::RSA); + ASSERT_TRUE(ctx); + ASSERT_TRUE(ctx.initForKeygen()); + ASSERT_TRUE(ctx.setRsaKeygenBits(2048)); + EVP_PKEY* raw = nullptr; + ASSERT_EQ(EVP_PKEY_keygen(ctx.get(), &raw), 1); + EVPKeyPointer key(raw); + + ncrypto::X509Pointer cert(X509_new()); + ASSERT_TRUE(cert); + ASSERT_EQ(X509_set_version(cert.get(), 2), 1); + ASSERT_EQ(ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1), 1); + ASSERT_EQ( + ASN1_TIME_set_string(X509_getm_notBefore(cert.get()), "20260101000000Z"), + 1); + ASSERT_EQ( + ASN1_TIME_set_string(X509_getm_notAfter(cert.get()), "20270101000000Z"), + 1); + X509NamePointer name(X509_NAME_new()); + ASSERT_TRUE(name); + ASSERT_EQ(X509_NAME_add_entry_by_txt( + name.get(), + "CN", + MBSTRING_ASC, + reinterpret_cast("issuer"), + -1, + -1, + 0), + 1); + ASSERT_EQ(X509_set_subject_name(cert.get(), name.get()), 1); + ASSERT_EQ(X509_set_issuer_name(cert.get(), name.get()), 1); + ASSERT_EQ(X509_set_pubkey(cert.get(), key.get()), 1); + ASSERT_GT(X509_sign(cert.get(), key.get(), EVP_sha256()), 0); + + X509NamePointer mismatch_name(X509_NAME_dup(name.get())); + ASSERT_TRUE(mismatch_name); + ASSERT_EQ(X509_NAME_add_entry_by_txt( + mismatch_name.get(), + "OU", + MBSTRING_ASC, + reinterpret_cast("other"), + -1, + -1, + 0), + 1); + + ncrypto::DeleteFnPtr invalid_value( + ASN1_OCTET_STRING_new()); + ASSERT_TRUE(invalid_value); + const unsigned char truncated_sequence[] = {0x30}; + ASSERT_EQ( + ASN1_OCTET_STRING_set( + invalid_value.get(), truncated_sequence, sizeof(truncated_sequence)), + 1); + X509ExtensionPointer invalid_constraints(X509_EXTENSION_create_by_NID( + nullptr, NID_basic_constraints, 0, invalid_value.get())); + ASSERT_TRUE(invalid_constraints); + + for (const bool seed_error : {true, false}) { + // Reparse each issuer so extension-cache failures are exercised each time. + auto matching_issuer = cert.view().clone(); + auto mismatch = cert.view().clone(); + auto malformed = cert.view().clone(); + ASSERT_TRUE(matching_issuer); + ASSERT_TRUE(mismatch); + ASSERT_TRUE(malformed); + ASSERT_EQ(X509_set_subject_name(mismatch.get(), mismatch_name.get()), 1); + ASSERT_EQ(X509_add_ext(malformed.get(), invalid_constraints.get(), -1), 1); + + const auto check = [seed_error](ncrypto::X509View subject, + ncrypto::X509View issuer, + bool issued) { + ERR_clear_error(); + if (seed_error) { +#if NCRYPTO_USE_OPENSSL_PROVIDER + ERR_raise(ERR_LIB_USER, 1); +#elif NCRYPTO_USE_BORINGSSL + ERR_put_error(ERR_LIB_USER, 0, 1, __FILE__, __LINE__); +#else +#error "Unsupported crypto backend" +#endif + } + const auto expected = ERR_peek_error(); + if (seed_error) ASSERT_NE(expected, 0UL); + EXPECT_EQ(subject.isIssuedBy(issuer), issued); + EXPECT_EQ(ERR_get_error(), expected); + EXPECT_EQ(ERR_get_error(), 0UL); + }; + + check(cert.view(), matching_issuer.view(), true); + check(cert.view(), mismatch.view(), false); + check(cert.view(), malformed.view(), false); + check({}, matching_issuer.view(), false); + check(cert.view(), {}, false); + } +} + TEST(NodeCrypto, X509KeyVisitorsHandleMissingPublicKey) { ncrypto::X509Pointer cert(X509_new()); ASSERT_TRUE(cert); From 0002058aa2a6d8936ba306609dd80ce4cbb0309a Mon Sep 17 00:00:00 2001 From: Huang-404-Q <129407632+Huang-404-Q@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:31:19 +0800 Subject: [PATCH 009/195] http2: clear the session timeout before marking it destroyed closeSession() set SESSION_FLAGS_DESTROYED and then called session.setTimeout(0). setStreamTimeout() returns early when the session is destroyed, so the idle timer stayed armed and retained the session until the server timeout fired. Clear the timeout and remove its listeners before setting the destroyed flag. Fixes: https://github.com/nodejs/node/issues/66885 Signed-off-by: huang <13165998033@163.com> PR-URL: https://github.com/nodejs/node/pull/66886 Reviewed-By: Tim Perry --- lib/internal/http2/core.js | 9 +++-- ...ion-timeout-cleared-on-close-raw-socket.js | 38 +++++++++++++++++++ ...-http2-session-timeout-cleared-on-close.js | 32 ++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-http2-session-timeout-cleared-on-close-raw-socket.js create mode 100644 test/parallel/test-http2-session-timeout-cleared-on-close.js diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 011cf22210ff..04acb5d30f63 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -1303,13 +1303,16 @@ function closeSession(session, code, error) { debugSessionObj(session, 'start closing/destroying', error); const state = session[kState]; - state.flags |= SESSION_FLAGS_DESTROYED; - state.destroyCode = code; - // Clear timeout and remove timeout listeners. + // Clear timeout and remove timeout listeners before marking the session + // destroyed: setTimeout() is a no-op once destroyed, so the idle timer + // would otherwise stay armed and retain the session. session.setTimeout(0); session.removeAllListeners('timeout'); + state.flags |= SESSION_FLAGS_DESTROYED; + state.destroyCode = code; + const socket = session[kSocket]; const handle = session[kHandle]; diff --git a/test/parallel/test-http2-session-timeout-cleared-on-close-raw-socket.js b/test/parallel/test-http2-session-timeout-cleared-on-close-raw-socket.js new file mode 100644 index 000000000000..1df70ec9b522 --- /dev/null +++ b/test/parallel/test-http2-session-timeout-cleared-on-close-raw-socket.js @@ -0,0 +1,38 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); +const net = require('net'); + +// Same as test-http2-session-timeout-cleared-on-close.js, but the client's +// TCP socket is destroyed without sending GOAWAY. +const server = http2.createServer(); +server.setTimeout(30_000, common.mustNotCall()); + +server.on('session', common.mustCall((session) => { + session.on('goaway', common.mustNotCall()); + session.on('close', common.mustCall(() => { + assert.strictEqual(session.timeout, 0); + server.close(); + })); +})); + +server.on('stream', common.mustCall((stream) => { + stream.respond({ ':status': 200 }); + stream.end('ok'); +})); + +server.listen(0, common.mustCall(() => { + let socket; + const client = http2.connect(`http://localhost:${server.address().port}`, { + createConnection: (url) => (socket = net.connect(url.port, url.hostname)), + }); + client.on('error', () => {}); + const req = client.request(); + req.on('error', () => {}); + req.resume(); + req.on('end', common.mustCall(() => socket.destroy())); +})); diff --git a/test/parallel/test-http2-session-timeout-cleared-on-close.js b/test/parallel/test-http2-session-timeout-cleared-on-close.js new file mode 100644 index 000000000000..476e60702ab5 --- /dev/null +++ b/test/parallel/test-http2-session-timeout-cleared-on-close.js @@ -0,0 +1,32 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const assert = require('assert'); +const http2 = require('http2'); + +// A destroyed session must not keep its idle timer armed: setTimeout() is a +// no-op once the session is marked destroyed, so closeSession() has to clear +// the timer first. +const server = http2.createServer(); +server.setTimeout(30_000, common.mustNotCall()); + +server.on('session', common.mustCall((session) => { + session.on('close', common.mustCall(() => { + assert.strictEqual(session.timeout, 0); + server.close(); + })); +})); + +server.on('stream', common.mustCall((stream) => { + stream.respond({ ':status': 200 }); + stream.end('ok'); +})); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}`); + const req = client.request(); + req.resume(); + req.on('end', common.mustCall(() => client.close())); +})); From dc4ef78fe3425cfcd7dad3d151b24c36629da39a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:33:23 +0100 Subject: [PATCH 010/195] meta: bump github/codeql-action/init from 4.38.2 to 4.39.1 Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.38.2 to 4.39.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.38.2 to 4.39.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.38.2 to 4.39.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.38.2 to 4.39.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: github/codeql-action/upload-sarif dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: github/codeql-action/autobuild dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: github/codeql-action/analyze dependency-version: 4.39.1 dependency-type: direct:production update-type: version-update:semver-minor ... PR-URL: https://github.com/nodejs/node/pull/67028 Reviewed-By: Christian Clauss Reviewed-By: Colin Ihrig --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d8eca1f2b798..6ad66a72676a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,15 +30,15 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: languages: ${{ matrix.language }} config-file: ./.github/codeql-config.yml - name: Autobuild - uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 2663019bcc2a..ee20d5a045ef 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -76,6 +76,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: sarif_file: results.sarif From 997d0d69909e53711eb6858781293abec8031f33 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Wed, 28 Oct 2026 09:56:55 -0400 Subject: [PATCH 011/195] zlib: skip the stream in sync convenience calls without options `zlib.inflateSync(buffer)` and the other synchronous convenience methods constructed a Transform stream for every call, even though only its native handle is used and, without options, the stream is never exposed. They also allocated a fresh output chunk for every call. When no options are passed, drive a native handle created with the default settings directly, and decompress into a scratch buffer kept between calls, copying the result out. Calls with options, including `info`, keep the existing path. Results and errors are unchanged. Signed-off-by: Yagiz Nizipli Assisted-by: Grok PR-URL: https://github.com/nodejs/node/pull/66864 Reviewed-By: Matteo Collina --- lib/zlib.js | 181 ++++++++++++++++-- .../test-zlib-sync-without-options.js | 136 +++++++++++++ 2 files changed, 298 insertions(+), 19 deletions(-) create mode 100644 test/parallel/test-zlib-sync-without-options.js diff --git a/lib/zlib.js b/lib/zlib.js index 3425233cbb67..c084889b6981 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -31,6 +31,7 @@ const { ObjectFreeze, ObjectKeys, ReflectApply, + SafeMap, Symbol, SymbolHasInstance, Uint32Array, @@ -176,43 +177,128 @@ function zlibBufferOnEnd() { this.cb(null, buf); } -function zlibBufferSync(engine, buffer) { +function toSyncInput(buffer) { if (typeof buffer === 'string') { - buffer = Buffer.from(buffer); - } else if (!isArrayBufferView(buffer)) { + return Buffer.from(buffer); + } + if (!isArrayBufferView(buffer)) { if (isAnyArrayBuffer(buffer)) { - buffer = Buffer.from(buffer); - } else { - throw new ERR_INVALID_ARG_TYPE( - 'buffer', - ['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], - buffer, - ); + return Buffer.from(buffer); } + throw new ERR_INVALID_ARG_TYPE( + 'buffer', + ['string', 'Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'], + buffer, + ); } + return buffer; +} + +function zlibBufferSync(engine, buffer) { + buffer = toSyncInput(buffer); buffer = processChunkSync(engine, buffer, engine._finishFlushFlag); if (engine._info) return { buffer, engine }; return buffer; } +function zlibError(message, errno, code) { + if (code === 'ERR_TRAILING_JUNK_AFTER_STREAM_END') { + return new ERR_TRAILING_JUNK_AFTER_STREAM_END(); + } + const error = genericNodeError(message, { errno, code }); + error.errno = errno; + error.code = code; + return error; +} + function zlibOnError(message, errno, code) { const self = this[owner_symbol]; // There is no way to cleanly recover. // Continuing only obscures problems. - - let error; - if (code === 'ERR_TRAILING_JUNK_AFTER_STREAM_END') { - error = new ERR_TRAILING_JUNK_AFTER_STREAM_END(); - } else { - error = genericNodeError(message, { errno, code }); - error.errno = errno; - error.code = code; - } + const error = zlibError(message, errno, code); self.destroy(error); self[kError] = error; } +// Without options, a synchronous convenience call (zlib.inflateSync(buffer), +// ...) uses the defaults and its engine is never exposed, so it drives a +// native handle directly instead of constructing a Transform stream. It also +// decompresses into a scratch buffer kept between calls and copies the result +// out, instead of allocating a whole output chunk per call. +let syncScratchBuffer; + +function zlibSyncOnError(message, errno, code) { + this[kError] = zlibError(message, errno, code); +} + +function zlibBufferSyncWithDefaults(ctor, buffer) { + const chunk = toSyncInput(buffer); + const mode = kEngineModes.get(ctor); + const writeState = new Uint32Array(2); + const handle = createDefaultHandle(mode, writeState); + handle.onerror = zlibSyncOnError; + // Take the scratch buffer, so that a nested call (e.g. from an async hook) + // allocates its own. + const scratch = syncScratchBuffer ?? Buffer.allocUnsafeSlow(Z_DEFAULT_CHUNK); + syncScratchBuffer = undefined; + try { + return processSyncWithDefaults(handle, writeState, chunk, + defaultFinishFlush(mode), scratch); + } finally { + handle.close(); + syncScratchBuffer = scratch; + } +} + +// Same as processChunkSync() for an engine created without options. +function processSyncWithDefaults(handle, writeState, chunk, flushFlag, + scratch) { + const chunkSize = Z_DEFAULT_CHUNK; + let out = scratch; + let inOff = 0; + let availIn = chunk.byteLength; + let buffers = null; + let nread = 0; + + while (true) { + handle.writeSync(flushFlag, chunk, inOff, availIn, out, 0, chunkSize); + if (handle[kError] !== undefined) + throw handle[kError]; + + const availOutAfter = writeState[0]; + const availInAfter = writeState[1]; + const have = chunkSize - availOutAfter; + if (have > 0) { + nread += have; + if (nread > kMaxLength) + throw new ERR_BUFFER_TOO_LARGE(kMaxLength); + if (out !== scratch) { + buffers.push(out.subarray(0, have)); + } else if (availOutAfter === 0) { + buffers = [out]; + } + } + if (availOutAfter !== 0) + break; + + // The output buffer is full. Continue with the rest of the input. + inOff += availIn - availInAfter; + availIn = availInAfter; + out = Buffer.allocUnsafe(chunkSize); + } + + if (nread === 0) + return new FastBuffer(); + if (buffers === null) { + // All of the output is in the scratch buffer. + const result = Buffer.allocUnsafe(nread); + scratch.copy(result, 0, 0, nread); + return result; + } + return Buffer.concat(buffers, nread); +} + const FLUSH_BOUND = [ [ Z_NO_FLUSH, Z_BLOCK ], [ BROTLI_OPERATION_PROCESS, BROTLI_OPERATION_EMIT_METADATA ], @@ -794,6 +880,8 @@ class Unzip extends Zlib { function createConvenienceMethod(ctor, sync, prepareOpts) { if (sync) { return function syncBufferWrapper(buffer, opts) { + if (opts === undefined) + return zlibBufferSyncWithDefaults(ctor, buffer); return zlibBufferSync(new ctor(opts), buffer); }; } @@ -999,6 +1087,61 @@ class ZstdDecompress extends Zstd { } } +const kEngineModes = new SafeMap([ + [Deflate, DEFLATE], + [Inflate, INFLATE], + [Gzip, GZIP], + [Gunzip, GUNZIP], + [DeflateRaw, DEFLATERAW], + [InflateRaw, INFLATERAW], + [Unzip, UNZIP], + [BrotliCompress, BROTLI_ENCODE], + [BrotliDecompress, BROTLI_DECODE], + [ZstdCompress, ZSTD_COMPRESS], + [ZstdDecompress, ZSTD_DECOMPRESS], +]); + +// Creates the native handle that the engine for `mode` creates when its +// constructor gets no options. +function createDefaultHandle(mode, writeState) { + let handle; + switch (mode) { + case BROTLI_ENCODE: + case BROTLI_DECODE: + handle = mode === BROTLI_DECODE ? + new binding.BrotliDecoder(mode) : new binding.BrotliEncoder(mode); + brotliInitParamsArray.fill(-1); + handle.init(brotliInitParamsArray, writeState, processCallback, undefined); + return handle; + case ZSTD_COMPRESS: + zstdInitCParamsArray.fill(-1); + handle = new binding.ZstdCompress(); + handle.init(zstdInitCParamsArray, undefined, writeState, processCallback, + undefined, false); + return handle; + case ZSTD_DECOMPRESS: + zstdInitDParamsArray.fill(-1); + handle = new binding.ZstdDecompress(); + handle.init(zstdInitDParamsArray, undefined, writeState, processCallback, + undefined, false); + return handle; + default: + handle = new binding.Zlib(mode); + handle.init(Z_DEFAULT_WINDOWBITS, Z_DEFAULT_COMPRESSION, + Z_DEFAULT_MEMLEVEL, Z_DEFAULT_STRATEGY, writeState, + processCallback, undefined, false); + return handle; + } +} + +function defaultFinishFlush(mode) { + if (mode === BROTLI_ENCODE || mode === BROTLI_DECODE) + return brotliDefaultOpts.finishFlush; + if (mode === ZSTD_COMPRESS || mode === ZSTD_DECOMPRESS) + return zstdDefaultOpts.finishFlush; + return zlibDefaultOpts.finishFlush; +} + function createProperty(ctor) { return { __proto__: null, diff --git a/test/parallel/test-zlib-sync-without-options.js b/test/parallel/test-zlib-sync-without-options.js new file mode 100644 index 000000000000..d155f9264429 --- /dev/null +++ b/test/parallel/test-zlib-sync-without-options.js @@ -0,0 +1,136 @@ +'use strict'; +// Synchronous convenience methods called without options skip the stream and +// reuse an internal output buffer. Check that they behave exactly like calls +// that pass an empty options object, which go through the stream. + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const fs = require('fs'); +const zlib = require('zlib'); + +const { Z_DEFAULT_CHUNK } = zlib.constants; + +const pairs = [ + ['deflateSync', 'inflateSync'], + ['gzipSync', 'gunzipSync'], + ['gzipSync', 'unzipSync'], + ['deflateRawSync', 'inflateRawSync'], + ['brotliCompressSync', 'brotliDecompressSync'], + ['zstdCompressSync', 'zstdDecompressSync'], +]; + +const random = Buffer.alloc(3 * Z_DEFAULT_CHUNK); +{ + let seed = 0x9e3779b9; + for (let i = 0; i < random.length; i++) { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + random[i] = seed & 0xff; + } +} +const text = fs.readFileSync(__filename); + +// Outputs that fit in one output buffer, fill it exactly, and span several. +const inputs = [ + Buffer.alloc(0), + Buffer.from('hello'), + text, + Buffer.alloc(Z_DEFAULT_CHUNK, 'a'), + Buffer.alloc(Z_DEFAULT_CHUNK + 1, 'b'), + Buffer.concat([text, random, text]), +]; + +for (const [compress, decompress] of pairs) { + for (const input of inputs) { + const label = `${compress}/${decompress} ${input.length}`; + const compressed = zlib[compress](input); + assert.ok(Buffer.isBuffer(compressed), label); + assert.deepStrictEqual(compressed, zlib[compress](input, {}), label); + + const decompressed = zlib[decompress](compressed); + assert.ok(Buffer.isBuffer(decompressed), label); + assert.deepStrictEqual(decompressed, input, label); + assert.deepStrictEqual(decompressed, zlib[decompress](compressed, {}), + label); + } + + // Every input type. + const expected = zlib[decompress](zlib[compress]('blah'.repeat(8))); + for (const view of [ + 'blah'.repeat(8), + ...common.getBufferSources(Buffer.from('blah'.repeat(8))), + ]) { + assert.deepStrictEqual(zlib[decompress](zlib[compress](view)), expected); + } +} + +// Results never share memory with each other. +{ + const a = zlib.deflateSync('first'); + const b = zlib.deflateSync('second'); + const first = zlib.inflateSync(a); + const second = zlib.inflateSync(b); + assert.strictEqual(first.toString(), 'first'); + assert.strictEqual(second.toString(), 'second'); + second.fill(0); + assert.strictEqual(first.toString(), 'first'); + assert.strictEqual(zlib.inflateSync(a).toString(), 'first'); +} + +// Errors are the same as through the stream. +for (const [method, input] of [ + ['inflateSync', Buffer.from('not compressed')], + ['gunzipSync', Buffer.from('not compressed')], + ['unzipSync', Buffer.from('not compressed')], + ['inflateRawSync', Buffer.from('not compressed')], + ['brotliDecompressSync', Buffer.from('not compressed')], + ['zstdDecompressSync', Buffer.from('not compressed')], + ['inflateSync', zlib.deflateSync(text).subarray(0, 100)], + ['gunzipSync', zlib.gzipSync(text).subarray(0, 100)], + ['brotliDecompressSync', zlib.brotliCompressSync(text).subarray(0, 100)], + ['zstdDecompressSync', zlib.zstdCompressSync(text).subarray(0, 100)], +]) { + let expected; + assert.throws(() => zlib[method](input, {}), (err) => { + expected = err; + return true; + }); + assert.throws(() => zlib[method](input), (err) => { + assert.strictEqual(err.constructor, expected.constructor); + assert.strictEqual(err.message, expected.message); + assert.strictEqual(err.code, expected.code); + assert.strictEqual(err.errno, expected.errno); + return true; + }, method); +} + +for (const [compress] of pairs) { + assert.throws(() => zlib[compress](42), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); +} + +// A synchronous call made from an async hook while another one is running. +{ + const compressed = zlib.deflateSync(text); + let nested; + const hook = async_hooks.createHook({ + before() { + if (nested === undefined) { + nested = null; + nested = zlib.inflateSync(compressed); + } + }, + }).enable(); + // The error is reported through a callback into JavaScript, which runs the + // before hook. + assert.throws(() => zlib.inflateSync(Buffer.from('not compressed')), { + code: 'Z_DATA_ERROR', + }); + hook.disable(); + assert.deepStrictEqual(nested, text); + assert.deepStrictEqual(zlib.inflateSync(compressed), text); +} From 86f2e27dff1fafebb5e1832fced34236b795c456 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 19 Oct 2026 12:08:21 -0400 Subject: [PATCH 012/195] zlib: finish async convenience calls in one write The asynchronous convenience methods (`zlib.deflate()`, `zlib.inflate()`, `zlib.gunzip()`, ...) wrote the input to a Transform stream and then ended it. That took two threadpool round trips, one for the data and one for the final flush, and ran the result through the stream machinery. The whole input is known up front, so unless `info` or a custom `flush` is requested, process it with the finish flush directly, the way the synchronous methods already do, and collect the output in the write callback. A call whose output fits in one buffer now takes a single threadpool round trip. The output is unchanged: the previous two-write sequence already produced the same bytes as the synchronous methods. `zstdCompress()` keeps the separate end-of-frame write, because a single `ZSTD_e_end` call can produce slightly larger frames. benchmark/compare.js, 40 runs, n=100000: confidence improvement zlib/inflate.js method='inflate' *** +94.91 % zlib/deflate.js method='deflate' *** +66.27 % Signed-off-by: Yagiz Nizipli Assisted-by: Grok PR-URL: https://github.com/nodejs/node/pull/66863 Reviewed-By: Robert Nagy Reviewed-By: Matteo Collina --- lib/zlib.js | 95 ++++-- .../test-zlib-convenience-async-round-trip.js | 74 +++++ test/parallel/test-zlib-convenience-async.js | 309 ++++++++++++++++++ 3 files changed, 454 insertions(+), 24 deletions(-) create mode 100644 test/parallel/test-zlib-convenience-async-round-trip.js create mode 100644 test/parallel/test-zlib-convenience-async.js diff --git a/lib/zlib.js b/lib/zlib.js index c084889b6981..e9458573577b 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -88,6 +88,7 @@ const { const kFlushFlag = Symbol('kFlushFlag'); const kError = Symbol('kError'); +const kBufferResult = Symbol('kBufferResult'); const constants = internalBinding('constants').zlib; const { @@ -125,7 +126,7 @@ for (const ckey of ObjectKeys(codes)) { codes[codes[ckey]] = ckey; } -function zlibBuffer(engine, buffer, callback) { +function zlibBuffer(engine, buffer, callback, finishInOneWrite) { validateFunction(callback, 'callback'); // Streams do not support non-Uint8Array ArrayBufferViews yet. Convert it to a // Buffer without copying. @@ -137,44 +138,81 @@ function zlibBuffer(engine, buffer, callback) { engine.buffers = null; engine.nread = 0; engine.cb = callback; - engine.on('data', zlibBufferOnData); engine.on('error', zlibBufferOnError); + + // The whole input is known up front. Unless the engine is handed to the + // caller (`info`) or a custom flush is requested, process it with the finish + // flush directly, like the synchronous methods do. This takes one threadpool + // round trip instead of a write followed by a separate final flush, and skips + // the stream machinery. + if (finishInOneWrite && !engine._info && + engine._defaultFlushFlag === FLUSH_BOUND[engine._flushBoundIdx][0]) { + if (typeof buffer === 'string') { + // Encode strings like Writable#write() does. + buffer = Buffer.from(buffer, engine._writableState.defaultEncoding); + } + if (isUint8Array(buffer)) { + engine[kBufferResult] = true; + processChunk(engine, buffer, engine._finishFlushFlag, + zlibBufferOnWriteDone); + return; + } + } + + engine.on('data', zlibBufferOnData); engine.on('end', zlibBufferOnEnd); engine.end(buffer); } -function zlibBufferOnData(chunk) { - if (!this.buffers) { - this.buffers = [chunk]; +function zlibBufferAddChunk(engine, chunk) { + if (!engine.buffers) { + engine.buffers = [chunk]; } else { - this.buffers.push(chunk); + engine.buffers.push(chunk); } - this.nread += chunk.length; - if (this.nread > this._maxOutputLength) { - this.close(); - this.removeAllListeners('end'); - this.cb(new ERR_BUFFER_TOO_LARGE(this._maxOutputLength)); + engine.nread += chunk.length; + if (engine.nread > engine._maxOutputLength) { + engine.close(); + engine.removeAllListeners('end'); + engine.cb(new ERR_BUFFER_TOO_LARGE(engine._maxOutputLength)); } } +function zlibBufferOnData(chunk) { + zlibBufferAddChunk(this, chunk); +} + function zlibBufferOnError(err) { this.removeAllListeners('end'); this.cb(err); } -function zlibBufferOnEnd() { +function zlibBufferFinish(engine) { let buf; - if (this.nread === 0) { + if (engine.nread === 0) { buf = new FastBuffer(); } else { - const bufs = this.buffers; - buf = (bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, this.nread)); + const bufs = engine.buffers; + buf = (bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, engine.nread)); } - this.close(); - if (this._info) - this.cb(null, { buffer: buf, engine: this }); + engine.close(); + if (engine._info) + engine.cb(null, { buffer: buf, engine }); else - this.cb(null, buf); + engine.cb(null, buf); +} + +function zlibBufferOnEnd() { + zlibBufferFinish(this); +} + +// Called by processCallback(), with the handle as `this`, once the whole input +// has been processed. Errors have already been reported to the callback, either +// through the 'error' event or by zlibBufferAddChunk(). +function zlibBufferOnWriteDone(err) { + const engine = this[owner_symbol]; + if (err === undefined && !engine.destroyed) + zlibBufferFinish(engine); } function toSyncInput(buffer) { @@ -638,7 +676,10 @@ function processCallback() { if (have > 0) { const out = self._outBuffer.slice(self._outOffset, self._outOffset + have); self._outOffset += have; - streamBufferIsFull = !self.push(out); + if (self[kBufferResult] === true) + zlibBufferAddChunk(self, out); + else + streamBufferIsFull = !self.push(out); } else { assert(have === 0, 'have should not go down'); } @@ -704,7 +745,8 @@ function processCallback() { return; } - self.push(null); + if (self[kBufferResult] !== true) + self.push(null); } // Finished with the chunk. @@ -877,7 +919,8 @@ class Unzip extends Zlib { } } -function createConvenienceMethod(ctor, sync, prepareOpts) { +function createConvenienceMethod(ctor, sync, prepareOpts, + finishInOneWrite = true) { if (sync) { return function syncBufferWrapper(buffer, opts) { if (opts === undefined) @@ -893,7 +936,7 @@ function createConvenienceMethod(ctor, sync, prepareOpts) { if (prepareOpts !== undefined) { opts = prepareOpts(buffer, opts); } - return zlibBuffer(new ctor(opts), buffer, callback); + return zlibBuffer(new ctor(opts), buffer, callback, finishInOneWrite); }; } @@ -1272,7 +1315,11 @@ module.exports = { brotliCompressSync: createConvenienceMethod(BrotliCompress, true), brotliDecompress: createConvenienceMethod(BrotliDecompress, false), brotliDecompressSync: createConvenienceMethod(BrotliDecompress, true), - zstdCompress: createConvenienceMethod(ZstdCompress, false, withPledgedSrcSize), + // A zstd frame written by a single ZSTD_e_end call can be a few bytes larger + // than one written by ZSTD_e_continue followed by ZSTD_e_end, so + // zstdCompress() keeps writing the input and ending the frame separately. + zstdCompress: createConvenienceMethod(ZstdCompress, false, withPledgedSrcSize, + false), zstdCompressSync: createConvenienceMethod(ZstdCompress, true), zstdDecompress: createConvenienceMethod(ZstdDecompress, false), zstdDecompressSync: createConvenienceMethod(ZstdDecompress, true), diff --git a/test/parallel/test-zlib-convenience-async-round-trip.js b/test/parallel/test-zlib-convenience-async-round-trip.js new file mode 100644 index 000000000000..ab48f8852729 --- /dev/null +++ b/test/parallel/test-zlib-convenience-async-round-trip.js @@ -0,0 +1,74 @@ +'use strict'; +// The asynchronous convenience methods know the whole input up front, so they +// process it with the finish flush in a single write. Check that a call whose +// output fits in one output buffer takes exactly one threadpool round trip. + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const zlib = require('zlib'); + +const input = Buffer.from('hello world '.repeat(64)); +// [method, argument, function that turns the result back into `input`] +const cases = [ + ['deflate', input, zlib.inflateSync], + ['inflate', zlib.deflateSync(input)], + ['gzip', input, zlib.gunzipSync], + ['gunzip', zlib.gzipSync(input)], + ['unzip', zlib.gzipSync(input)], + ['deflateRaw', input, zlib.inflateRawSync], + ['inflateRaw', zlib.deflateRawSync(input)], + ['brotliCompress', input, zlib.brotliDecompressSync], + ['brotliDecompress', zlib.brotliCompressSync(input)], + ['zstdDecompress', zlib.zstdCompressSync(input)], +]; + +// Counts the callbacks into JavaScript (`before` hooks) of each ZLIB resource +// created while `fn` runs. +async function countRoundTrips(fn) { + const callbacks = new Map(); + const hook = async_hooks.createHook({ + init(asyncId, type) { + if (type === 'ZLIB') callbacks.set(asyncId, 0); + }, + before(asyncId) { + if (callbacks.has(asyncId)) + callbacks.set(asyncId, callbacks.get(asyncId) + 1); + }, + }).enable(); + try { + await fn(); + } finally { + hook.disable(); + } + return [...callbacks.values()]; +} + +function callAsync(method, buffer, options = {}) { + return new Promise((resolve, reject) => { + zlib[method](buffer, options, common.mustCall((err, result) => { + if (err) reject(err); + else resolve(result); + })); + }); +} + +(async () => { + for (const [method, buffer, undo = (result) => result] of cases) { + let result; + const roundTrips = await countRoundTrips(async () => { + result = await callAsync(method, buffer); + }); + assert.deepStrictEqual(roundTrips, [1], method); + assert.deepStrictEqual(undo(result), input, method); + } + + // Errors are also reported after a single round trip. + let error; + const roundTrips = await countRoundTrips(async () => { + error = await callAsync('inflate', Buffer.from('not zlib data')) + .catch((err) => err); + }); + assert.deepStrictEqual(roundTrips, [1]); + assert.strictEqual(error.code, 'Z_DATA_ERROR'); +})().then(common.mustCall()); diff --git a/test/parallel/test-zlib-convenience-async.js b/test/parallel/test-zlib-convenience-async.js new file mode 100644 index 000000000000..c66f259fea01 --- /dev/null +++ b/test/parallel/test-zlib-convenience-async.js @@ -0,0 +1,309 @@ +'use strict'; +// Tests the contract of the asynchronous convenience methods +// (zlib.deflate(), zlib.gunzip(), zlib.brotliCompress(), ...). + +const common = require('../common'); +const assert = require('assert'); +const { AsyncLocalStorage } = require('async_hooks'); +const fs = require('fs'); +const { promisify } = require('util'); +const zlib = require('zlib'); + +const { + Z_SYNC_FLUSH, + Z_FULL_FLUSH, + Z_BUF_ERROR, + Z_DATA_ERROR, + Z_NEED_DICT, + Z_MIN_CHUNK, + Z_DEFAULT_CHUNK, + BROTLI_OPERATION_FLUSH, + ZSTD_e_flush, +} = zlib.constants; + +const pairs = [ + { compress: 'deflate', decompress: 'inflate', engine: 'Inflate' }, + { compress: 'gzip', decompress: 'gunzip', engine: 'Gunzip' }, + { compress: 'gzip', decompress: 'unzip', engine: 'Unzip' }, + { compress: 'deflateRaw', decompress: 'inflateRaw', engine: 'InflateRaw' }, + { + compress: 'brotliCompress', + decompress: 'brotliDecompress', + engine: 'BrotliDecompress', + partialFlush: BROTLI_OPERATION_FLUSH, + }, + { + compress: 'zstdCompress', + decompress: 'zstdDecompress', + engine: 'ZstdDecompress', + partialFlush: ZSTD_e_flush, + }, +]; + +// Calls an asynchronous convenience method and resolves with its result. +// The callback must be called exactly once, and never synchronously. +function callAsync(method, input, options) { + return new Promise((resolve, reject) => { + let returned = false; + const callback = common.mustCall((err, result) => { + assert.strictEqual(returned, true, + `${method}() called its callback synchronously`); + if (err) reject(err); + else resolve(result); + }); + if (options === undefined) { + zlib[method](input, callback); + } else { + zlib[method](input, options, callback); + } + returned = true; + }); +} + +// Text plus pseudo-random bytes, large enough that the output of every +// algorithm spans several Z_DEFAULT_CHUNK-sized output buffers. +const text = fs.readFileSync(__filename); +const random = Buffer.alloc(3 * Z_DEFAULT_CHUNK); +{ + let seed = 0x9e3779b9; + for (let i = 0; i < random.length; i++) { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + random[i] = seed & 0xff; + } +} +const data = Buffer.concat([text, random, Buffer.alloc(8 * Z_DEFAULT_CHUNK, 'a')]); +const shortString = 'blah'.repeat(8); + +async function testRoundTrip({ compress, decompress, partialFlush }) { + const syncCompress = `${compress}Sync`; + const syncDecompress = `${decompress}Sync`; + + // Every documented input type. + const inputs = [ + ['string', shortString, Buffer.from(shortString)], + ['long string', text.toString(), text], + ['Buffer', data, data], + ['large ArrayBuffer', new Uint8Array(data).buffer, data], + ...common.getBufferSources(Buffer.from(shortString)).map((view) => [ + view[Symbol.toStringTag], view, Buffer.from(shortString), + ]), + ]; + + for (const [type, input, expected] of inputs) { + const label = `${compress}/${decompress} with ${type}`; + const compressed = await callAsync(compress, input); + assert.ok(Buffer.isBuffer(compressed), label); + // zstdCompress() pledges the input size and ends the frame separately, + // which yields different (not larger) frames than zstdCompressSync(). + if (compress !== 'zstdCompress') { + assert.deepStrictEqual(compressed, zlib[syncCompress](input), label); + } + // Interoperable with the synchronous API in both directions. + assert.deepStrictEqual(zlib[syncDecompress](compressed), expected, label); + const decompressed = + await callAsync(decompress, zlib[syncCompress](input)); + assert.ok(Buffer.isBuffer(decompressed), label); + assert.deepStrictEqual(decompressed, expected, label); + assert.deepStrictEqual(await callAsync(decompress, compressed), expected, + label); + } + + // A minimal chunkSize makes the work for one call span many output + // buffers. + const options = { chunkSize: Z_MIN_CHUNK }; + const compressed = await callAsync(compress, data, options); + if (compress !== 'zstdCompress') { + assert.deepStrictEqual(compressed, zlib[syncCompress](data, options)); + } + assert.deepStrictEqual(await callAsync(decompress, compressed, options), + data); + + // Empty input. + const empty = await callAsync(compress, Buffer.alloc(0)); + assert.deepStrictEqual(await callAsync(decompress, empty), Buffer.alloc(0)); + assert.deepStrictEqual( + await callAsync(decompress, Buffer.alloc(0), + { finishFlush: partialFlush ?? Z_SYNC_FLUSH }), + Buffer.alloc(0)); +} + +async function testErrors({ compress, decompress, partialFlush }) { + const compressed = zlib[`${compress}Sync`](data); + + // Truncated input. + const truncated = compressed.subarray(0, compressed.length >> 1); + await assert.rejects(callAsync(decompress, truncated), { + name: 'Error', + code: 'Z_BUF_ERROR', + errno: Z_BUF_ERROR, + message: 'unexpected end of file', + }); + + // A non-finalizing finishFlush returns as much as can be decompressed. + const partial = await callAsync(decompress, truncated, { + finishFlush: partialFlush ?? Z_SYNC_FLUSH, + }); + // Zstd only emits whole blocks, and this input is truncated inside the + // first one. + if (decompress !== 'zstdDecompress') assert.ok(partial.length > 0); + assert.deepStrictEqual(partial, data.subarray(0, partial.length)); + + // maxOutputLength applies to the output of the whole call. + await assert.rejects(callAsync(decompress, compressed, + { maxOutputLength: data.length - 1 }), { + name: 'RangeError', + code: 'ERR_BUFFER_TOO_LARGE', + message: `Cannot create a Buffer larger than ${data.length - 1} bytes`, + }); + assert.deepStrictEqual( + await callAsync(decompress, compressed, { maxOutputLength: data.length }), + data); + await assert.rejects(callAsync(compress, data, { maxOutputLength: 64 }), { + name: 'RangeError', + code: 'ERR_BUFFER_TOO_LARGE', + message: 'Cannot create a Buffer larger than 64 bytes', + }); +} + +async function testInfo({ compress, decompress, engine }) { + const compressed = zlib[`${compress}Sync`](data); + const result = await callAsync(decompress, compressed, { info: true }); + assert.deepStrictEqual(result.buffer, data); + assert.ok(result.engine instanceof zlib[engine]); + assert.strictEqual(result.engine.bytesWritten, compressed.length); +} + +async function testTrailingGarbage({ compress, decompress }) { + const compressed = zlib[`${compress}Sync`](data); + const withJunk = Buffer.concat([compressed, Buffer.from('junk')]); + await assert.rejects( + callAsync(decompress, withJunk, { rejectGarbageAfterEnd: true }), { + name: 'TypeError', + code: 'ERR_TRAILING_JUNK_AFTER_STREAM_END', + }); + // Gzip treats bytes after a member as the start of another member. + if (decompress !== 'gunzip' && decompress !== 'unzip') { + assert.deepStrictEqual(await callAsync(decompress, withJunk), data); + } +} + +(async () => { + for (const pair of pairs) { + await testRoundTrip(pair); + await testErrors(pair); + await testInfo(pair); + await testTrailingGarbage(pair); + } + + // Invalid compressed data. + for (const [method, expected] of [ + ['inflate', { code: 'Z_DATA_ERROR', errno: Z_DATA_ERROR, + message: 'incorrect header check' }], + ['gunzip', { code: 'Z_DATA_ERROR', errno: Z_DATA_ERROR, + message: 'incorrect header check' }], + ['unzip', { code: 'Z_DATA_ERROR', errno: Z_DATA_ERROR, + message: 'incorrect header check' }], + ['inflateRaw', { code: 'Z_DATA_ERROR', errno: Z_DATA_ERROR }], + ['brotliDecompress', { code: /^ERR__ERROR_FORMAT_/, + message: 'Decompression failed' }], + ['zstdDecompress', { code: 'ZSTD_error_prefix_unknown', + message: 'Unknown frame descriptor' }], + ]) { + await assert.rejects( + callAsync(method, Buffer.from('this is not compressed data')), + { name: 'Error', ...expected }); + } + + // Concatenated gzip members are all decompressed. + { + const members = Buffer.concat([zlib.gzipSync('abc'), zlib.gzipSync('def')]); + assert.strictEqual((await callAsync('gunzip', members)).toString(), + 'abcdef'); + } + + // Dictionaries. + { + const dictionary = Buffer.from('a dictionary for the zlib tests'); + const input = 'zlib tests, a dictionary for the zlib tests'; + const compressed = await callAsync('deflate', input, { dictionary }); + assert.strictEqual( + (await callAsync('inflate', compressed, { dictionary })).toString(), + input); + await assert.rejects(callAsync('inflate', compressed), { + code: 'Z_NEED_DICT', + errno: Z_NEED_DICT, + message: 'Missing dictionary', + }); + await assert.rejects( + callAsync('inflate', compressed, { dictionary: Buffer.from('nope') }), { + code: 'Z_NEED_DICT', + message: 'Bad dictionary', + }); + } + + // Non-default flush and compression options still round-trip. + for (const options of [ + { flush: Z_SYNC_FLUSH }, + { flush: Z_FULL_FLUSH, level: 1 }, + { level: 9, memLevel: 9, windowBits: 9, strategy: zlib.constants.Z_RLE }, + ]) { + const compressed = await callAsync('deflate', data, options); + assert.deepStrictEqual(zlib.inflateSync(compressed, options), data); + } + + // Strings are encoded with the stream defaultEncoding option. + { + const compressed = await callAsync('deflate', 'ff00', + { defaultEncoding: 'hex' }); + assert.deepStrictEqual(zlib.inflateSync(compressed), + Buffer.from([0xff, 0x00])); + } + + // Many concurrent calls. + { + const inputs = Array.from({ length: 64 }, + (_, i) => data.subarray(i * 512, i * 1024 + 7)); + const results = await Promise.all(inputs.map(async (input) => { + const compressed = await callAsync('gzip', input); + return callAsync('gunzip', compressed); + })); + for (let i = 0; i < inputs.length; i++) { + assert.deepStrictEqual(results[i], inputs[i]); + } + } + + // util.promisify() works. + assert.deepStrictEqual( + zlib.inflateSync(await promisify(zlib.deflate)(data)), data); + + // The callback runs in the caller's AsyncLocalStorage context. + { + const als = new AsyncLocalStorage(); + const compressed = zlib.deflateSync(data); + await new Promise((resolve) => { + als.run('store', () => { + zlib.inflate(compressed, common.mustSucceed((result) => { + assert.strictEqual(als.getStore(), 'store'); + assert.deepStrictEqual(result, data); + resolve(); + })); + }); + }); + } + + // Invalid arguments throw synchronously without calling the callback. + assert.throws(() => zlib.deflate(1, common.mustNotCall()), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => zlib.deflate(data), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + assert.throws(() => zlib.deflate(data, { level: 42 }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + }); +})().then(common.mustCall()); From 209fb77c48d71cf8187c144a3b6429e17b6df1c8 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 19 Oct 2026 13:07:51 -0400 Subject: [PATCH 013/195] zlib: grow the output buffer of large async one-shot calls The asynchronous convenience methods hand the whole input to zlib in one write, but still take one threadpool round trip per `chunkSize` (16 KiB by default) of output. Decompressing 4 MiB took 257 round trips. Only the concatenated result of these calls is observable, so double the output buffer whenever it fills up, up to 1 MiB. 4 MiB of output now takes 10 round trips. The buffer never grows past what is needed to tell that `maxOutputLength` was exceeded, and results that fit in the first buffer are unchanged. Signed-off-by: Yagiz Nizipli Assisted-by: Grok PR-URL: https://github.com/nodejs/node/pull/66863 Reviewed-By: Robert Nagy Reviewed-By: Matteo Collina --- lib/zlib.js | 15 ++++ .../test-zlib-convenience-async-large.js | 82 +++++++++++++++++++ .../test-zlib-convenience-async-round-trip.js | 16 +++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-zlib-convenience-async-large.js diff --git a/lib/zlib.js b/lib/zlib.js index e9458573577b..f1d93665967c 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -24,6 +24,7 @@ const { ArrayBuffer, MathMax, + MathMin, NumberIsNaN, ObjectDefineProperties, ObjectDefineProperty, @@ -182,6 +183,18 @@ function zlibBufferOnData(chunk) { zlibBufferAddChunk(this, chunk); } +// Only the concatenated output of a one-shot call is observable, so its output +// buffer can double in size whenever it fills up, and large results take far +// fewer threadpool round trips. It never grows past what is needed to detect +// that maxOutputLength was exceeded. +const kMaxBufferResultChunkSize = 1024 * 1024; +function zlibBufferGrowChunkSize(engine) { + const size = MathMin(engine._chunkSize * 2, kMaxBufferResultChunkSize, + engine._maxOutputLength - engine.nread + 1); + if (size > engine._chunkSize) + engine._chunkSize = size; +} + function zlibBufferOnError(err) { this.removeAllListeners('end'); this.cb(err); @@ -691,6 +704,8 @@ function processCallback() { // Exhausted the output buffer, or used all the input create a new one. if (availOutAfter === 0 || self._outOffset >= self._chunkSize) { + if (availOutAfter === 0 && self[kBufferResult] === true) + zlibBufferGrowChunkSize(self); handle.availOutBefore = self._chunkSize; self._outOffset = 0; self._outBuffer = Buffer.allocUnsafe(self._chunkSize); diff --git a/test/parallel/test-zlib-convenience-async-large.js b/test/parallel/test-zlib-convenience-async-large.js new file mode 100644 index 000000000000..03a7dab86843 --- /dev/null +++ b/test/parallel/test-zlib-convenience-async-large.js @@ -0,0 +1,82 @@ +'use strict'; +// The asynchronous convenience methods grow their output buffer while +// processing large inputs. Check that large results, custom chunkSize values +// and maxOutputLength behave exactly like the synchronous methods. + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const zlib = require('zlib'); + +const { Z_MIN_CHUNK, BROTLI_PARAM_QUALITY } = zlib.constants; + +// Text plus pseudo-random bytes, so that both the compressed and the +// decompressed output span many output buffers. +const random = Buffer.alloc(256 * 1024); +{ + let seed = 0x2545f491; + for (let i = 0; i < random.length; i++) { + seed ^= seed << 13; + seed ^= seed >>> 17; + seed ^= seed << 5; + random[i] = seed & 0xff; + } +} +const data = Buffer.concat([ + Buffer.alloc(2 * 1024 * 1024, fs.readFileSync(__filename)), + random, +]); + +// Keep brotli fast enough for a test. +const brotliOptions = { params: { [BROTLI_PARAM_QUALITY]: 4 } }; +const pairs = [ + ['deflate', 'inflate'], + ['gzip', 'gunzip'], + ['gzip', 'unzip'], + ['deflateRaw', 'inflateRaw'], + ['brotliCompress', 'brotliDecompress', brotliOptions], + ['zstdCompress', 'zstdDecompress'], +]; + +function callAsync(method, input, options) { + return new Promise((resolve, reject) => { + zlib[method](input, options, common.mustCall((err, result) => { + if (err) reject(err); + else resolve(result); + })); + }); +} + +(async () => { + for (const [compress, decompress, options = {}] of pairs) { + const label = `${compress}/${decompress}`; + const compressed = await callAsync(compress, data, options); + const expected = zlib[`${compress}Sync`](data, options); + // zstdCompress() pledges the input size and ends the frame separately, + // which yields different (not larger) frames than zstdCompressSync(). + if (compress !== 'zstdCompress') { + assert.deepStrictEqual(compressed, expected, label); + } + assert.deepStrictEqual(await callAsync(decompress, compressed, {}), data, + label); + + for (const chunkSize of [Z_MIN_CHUNK, data.length + 1]) { + assert.deepStrictEqual( + await callAsync(decompress, compressed, { chunkSize }), data, + `${label} chunkSize=${chunkSize}`); + } + + for (const maxOutputLength of [data.length - 1, 100000, 1]) { + await assert.rejects( + callAsync(decompress, compressed, { maxOutputLength }), { + name: 'RangeError', + code: 'ERR_BUFFER_TOO_LARGE', + message: `Cannot create a Buffer larger than ${maxOutputLength} bytes`, + }, `${label} maxOutputLength=${maxOutputLength}`); + } + assert.deepStrictEqual( + await callAsync(decompress, compressed, + { maxOutputLength: data.length }), + data, label); + } +})().then(common.mustCall()); diff --git a/test/parallel/test-zlib-convenience-async-round-trip.js b/test/parallel/test-zlib-convenience-async-round-trip.js index ab48f8852729..e7b9fb217b04 100644 --- a/test/parallel/test-zlib-convenience-async-round-trip.js +++ b/test/parallel/test-zlib-convenience-async-round-trip.js @@ -1,7 +1,8 @@ 'use strict'; // The asynchronous convenience methods know the whole input up front, so they // process it with the finish flush in a single write. Check that a call whose -// output fits in one output buffer takes exactly one threadpool round trip. +// output fits in one output buffer takes exactly one threadpool round trip, +// and that larger results need few round trips. const common = require('../common'); const assert = require('assert'); @@ -71,4 +72,17 @@ function callAsync(method, buffer, options = {}) { }); assert.deepStrictEqual(roundTrips, [1]); assert.strictEqual(error.code, 'Z_DATA_ERROR'); + + // The output buffer doubles from the 16 KiB default up to 1 MiB, so 4 MiB of + // output takes 6 + 4 round trips instead of 256. + { + const large = Buffer.alloc(4 * 1024 * 1024, input); + const compressed = zlib.deflateSync(large); + let result; + const roundTrips = await countRoundTrips(async () => { + result = await callAsync('inflate', compressed); + }); + assert.deepStrictEqual(roundTrips, [10]); + assert.deepStrictEqual(result, large); + } })().then(common.mustCall()); From 5b8a2e0937de74e4a70e81905eae9261cd41b1f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:21:29 +0000 Subject: [PATCH 014/195] tools: bump undici from 6.28.0 to 6.29.1 in /tools/doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [undici](https://github.com/nodejs/undici) from 6.28.0 to 6.29.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.28.0...v6.29.1) --- updated-dependencies: - dependency-name: undici dependency-version: 6.29.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67027 Reviewed-By: Trivikram Kamat Reviewed-By: Colin Ihrig Reviewed-By: Ulises Gascón --- tools/doc/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/doc/package-lock.json b/tools/doc/package-lock.json index 20912317efc8..42d6c3814f99 100644 --- a/tools/doc/package-lock.json +++ b/tools/doc/package-lock.json @@ -4572,9 +4572,9 @@ } }, "node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", "license": "MIT", "engines": { "node": ">=18.17" From 9b991787079c81fff35c62d4ca6848988b729a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:21:46 +0000 Subject: [PATCH 015/195] tools: bump piscina from 5.2.0 to 5.3.2 in /tools/doc Bumps [piscina](https://github.com/piscinajs/piscina) from 5.2.0 to 5.3.2. - [Release notes](https://github.com/piscinajs/piscina/releases) - [Changelog](https://github.com/piscinajs/piscina/blob/v5.3.2/CHANGELOG.md) - [Commits](https://github.com/piscinajs/piscina/compare/v5.2.0...v5.3.2) --- updated-dependencies: - dependency-name: piscina dependency-version: 5.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67026 Reviewed-By: Antoine du Hamel Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig --- tools/doc/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/doc/package-lock.json b/tools/doc/package-lock.json index 42d6c3814f99..e6dae3df5de5 100644 --- a/tools/doc/package-lock.json +++ b/tools/doc/package-lock.json @@ -3980,9 +3980,9 @@ } }, "node_modules/piscina": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.2.0.tgz", - "integrity": "sha512-DszUCKeVN/5G5QKo6jAVHL8fmKnkJvQ0ACiVgY7YGCq3TUB2oznAOayvZPIAdEThvhczkXR+qm3IHsNXpFCYfA==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.3.2.tgz", + "integrity": "sha512-vv7l/mM7B9WgrhDaudIqv1x6v4LOBRYYmpWGdWushhpbCmVHp11sUB2v4hj1CAeTGXVxuZFz6xOU1RalAiPKfQ==", "license": "MIT", "engines": { "node": ">=20.x" From 481145443e2b124d39bb50b49e51baa3eaa48f7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:22:00 +0000 Subject: [PATCH 016/195] meta: bump actions/upload-artifact from 7.0.1 to 7.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.1 to 7.0.2. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67024 Reviewed-By: Luigi Pinca Reviewed-By: Antoine du Hamel Reviewed-By: Ulises Gascón Reviewed-By: Tierney Cyren Reviewed-By: Colin Ihrig --- .github/workflows/benchmark.yml | 2 +- .github/workflows/build-tarball.yml | 2 +- .github/workflows/daily-wpt-fyi.yml | 2 +- .github/workflows/doc.yml | 2 +- .github/workflows/nix-changes.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/test-shared.yml | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 9f4ed58447df..226ae8119602 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -185,7 +185,7 @@ jobs: RUNS: ${{ inputs.runs }} - name: Upload raw benchmark results - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: csv-${{ matrix.system }} path: ${{ matrix.system }}.csv diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml index b3ac6aad475b..bea8e1dde3cb 100644 --- a/.github/workflows/build-tarball.yml +++ b/.github/workflows/build-tarball.yml @@ -91,7 +91,7 @@ jobs: export COMMIT=$(git rev-parse --short=10 "$GITHUB_SHA") ./configure && make tar -j4 SKIP_XZ=1 GZIP_COMPRESSION=6 - name: Upload tarball artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: path: '*.tar.gz' archive: false diff --git a/.github/workflows/daily-wpt-fyi.yml b/.github/workflows/daily-wpt-fyi.yml index b16b640a0843..5a7bca0f3959 100644 --- a/.github/workflows/daily-wpt-fyi.yml +++ b/.github/workflows/daily-wpt-fyi.yml @@ -128,7 +128,7 @@ jobs: NODE_VERSION: ${{ steps.setup-node.outputs.node-version }} - name: Upload GitHub Actions artifact if: ${{ env.WPT_REPORT != '' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: path: out/wpt/wptreport-*.json name: WPT Report for ${{ steps.setup-node.outputs.node-version }} diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 693d9ad3dd24..f2341c56c867 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -33,7 +33,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} - name: Build run: NODE=$(command -v node) make doc-only - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: docs path: out/doc diff --git a/.github/workflows/nix-changes.yml b/.github/workflows/nix-changes.yml index f55a0b7df135..792b5ffc72eb 100644 --- a/.github/workflows/nix-changes.yml +++ b/.github/workflows/nix-changes.yml @@ -97,7 +97,7 @@ jobs: - name: Upload requisites lists if: ${{ github.event.pull_request }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: requisites-${{ matrix.system }} path: | diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index ee20d5a045ef..994538ab4cde 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,7 +68,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml index 6e10cf953387..e6a2e6efbd92 100644 --- a/.github/workflows/test-shared.yml +++ b/.github/workflows/test-shared.yml @@ -140,7 +140,7 @@ jobs: DISTTYPE: nightly - name: Upload tarball artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: path: '*.tar.gz' archive: false @@ -222,7 +222,7 @@ jobs: - name: Upload libv8-aarch64-linux.nar if: ${{ steps.v8-drv.outputs.ALREADY_CACHED != 'true' && env.HAS_CACHIX_AUTH_TOKEN != 'true' }} id: upload - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: libv8-aarch64-linux.nar path: libv8-aarch64-linux.nar From ed4fe08c01d66b9da484d8ea5a940493b2da7f41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:22:13 +0000 Subject: [PATCH 017/195] meta: bump actions/download-artifact from 8.0.1 to 8.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 8.0.1 to 8.0.2. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 8.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67018 Reviewed-By: Luigi Pinca Reviewed-By: Antoine du Hamel Reviewed-By: Ulises Gascón --- .github/workflows/benchmark.yml | 2 +- .github/workflows/build-shared.yml | 4 ++-- .github/workflows/build-tarball.yml | 2 +- .github/workflows/nix-changes-comment.yml | 2 +- .github/workflows/test-shared.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 226ae8119602..f0f8f972e1c6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -209,7 +209,7 @@ jobs: sparse-checkout-cone-mode: false - name: Download benchmark raw results - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: csv-* merge-multiple: true diff --git a/.github/workflows/build-shared.yml b/.github/workflows/build-shared.yml index 73f7ec6afbbf..ad212b437778 100644 --- a/.github/workflows/build-shared.yml +++ b/.github/workflows/build-shared.yml @@ -41,7 +41,7 @@ jobs: build: runs-on: ${{ inputs.runner }} steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: tarballs pattern: '*.tar.gz' @@ -62,7 +62,7 @@ jobs: name: nodejs authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 if: inputs.v8-nar with: name: ${{ inputs.v8-nar }} diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml index bea8e1dde3cb..ecf390917e8f 100644 --- a/.github/workflows/build-tarball.yml +++ b/.github/workflows/build-tarball.yml @@ -119,7 +119,7 @@ jobs: with: version: v0.17.0 - name: Download tarball - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: tarballs - name: Extract tarball diff --git a/.github/workflows/nix-changes-comment.yml b/.github/workflows/nix-changes-comment.yml index db96ff4a09ca..1eccc6a710dc 100644 --- a/.github/workflows/nix-changes-comment.yml +++ b/.github/workflows/nix-changes-comment.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - name: Download artefacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: run-id: ${{ github.event.workflow_run.id }} # Using privileged token to be able to download file from a different workflow diff --git a/.github/workflows/test-shared.yml b/.github/workflows/test-shared.yml index e6a2e6efbd92..e43063438540 100644 --- a/.github/workflows/test-shared.yml +++ b/.github/workflows/test-shared.yml @@ -181,7 +181,7 @@ jobs: env: HAS_CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN && 'true' }} steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: tarballs From 5757587d8ea9f4f8d3173eb8da3f812291facc4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:34:52 +0000 Subject: [PATCH 018/195] meta: bump actions/setup-node from 7.0.0 to 7.1.0 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67032 Reviewed-By: Christian Clauss Reviewed-By: Trivikram Kamat Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca --- .github/workflows/auto-start-ci.yml | 2 +- .github/workflows/commit-lint.yml | 2 +- .github/workflows/commit-queue.yml | 2 +- .github/workflows/create-release-proposal.yml | 2 +- .github/workflows/daily-wpt-fyi.yml | 2 +- .github/workflows/daily.yml | 2 +- .github/workflows/doc.yml | 2 +- .github/workflows/find-inactive-collaborators.yml | 2 +- .github/workflows/find-inactive-tsc.yml | 2 +- .github/workflows/linters.yml | 6 +++--- .github/workflows/notify-on-push.yml | 2 +- .github/workflows/update-v8.yml | 2 +- .github/workflows/update-wpt.yml | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/auto-start-ci.yml b/.github/workflows/auto-start-ci.yml index 3c7872baab7d..c4a404825e18 100644 --- a/.github/workflows/auto-start-ci.yml +++ b/.github/workflows/auto-start-ci.yml @@ -64,7 +64,7 @@ jobs: runs-on: ubuntu-slim steps: - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/commit-lint.yml b/.github/workflows/commit-lint.yml index 6b173d8bc978..29b599d1d9e5 100644 --- a/.github/workflows/commit-lint.yml +++ b/.github/workflows/commit-lint.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-slim steps: - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Validate first commit message diff --git a/.github/workflows/commit-queue.yml b/.github/workflows/commit-queue.yml index 520b0239ff44..d0027dd6bd70 100644 --- a/.github/workflows/commit-queue.yml +++ b/.github/workflows/commit-queue.yml @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-slim steps: - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/create-release-proposal.yml b/.github/workflows/create-release-proposal.yml index 84300d8a1695..3b309460c01a 100644 --- a/.github/workflows/create-release-proposal.yml +++ b/.github/workflows/create-release-proposal.yml @@ -45,7 +45,7 @@ jobs: # Install dependencies - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/daily-wpt-fyi.yml b/.github/workflows/daily-wpt-fyi.yml index 5a7bca0f3959..d8ea4588fc4e 100644 --- a/.github/workflows/daily-wpt-fyi.yml +++ b/.github/workflows/daily-wpt-fyi.yml @@ -51,7 +51,7 @@ jobs: run: echo "NIGHTLY=$(curl -s https://nodejs.org/download/nightly/index.json | jq -r '[.[] | select(.files[] | contains("linux-arm64"))][0].version')" >> $GITHUB_ENV - name: Install Node.js id: setup-node - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NIGHTLY || matrix.node-version }} check-latest: true diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index b7a616e2df5d..9fb09d56bbba 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -20,7 +20,7 @@ jobs: with: persist-credentials: false - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Build lto diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index f2341c56c867..9d06f0b21247 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -28,7 +28,7 @@ jobs: with: persist-credentials: false - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Build diff --git a/.github/workflows/find-inactive-collaborators.yml b/.github/workflows/find-inactive-collaborators.yml index 8c74fda22428..61af1a2bf134 100644 --- a/.github/workflows/find-inactive-collaborators.yml +++ b/.github/workflows/find-inactive-collaborators.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/find-inactive-tsc.yml b/.github/workflows/find-inactive-tsc.yml index d59f26b73b2b..67ff96a67432 100644 --- a/.github/workflows/find-inactive-tsc.yml +++ b/.github/workflows/find-inactive-tsc.yml @@ -34,7 +34,7 @@ jobs: repository: nodejs/TSC - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index f6945f9df517..3f7e254b69f4 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -33,7 +33,7 @@ jobs: !/deps/ sparse-checkout-cone-mode: false - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Lint addon docs @@ -68,7 +68,7 @@ jobs: fetch-depth: 2 persist-credentials: false - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} @@ -105,7 +105,7 @@ jobs: persist-credentials: false - name: Use Node.js ${{ env.NODE_VERSION }} id: setup-node - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm diff --git a/.github/workflows/notify-on-push.yml b/.github/workflows/notify-on-push.yml index 25421b8447d8..dfded4d76af4 100644 --- a/.github/workflows/notify-on-push.yml +++ b/.github/workflows/notify-on-push.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Validate commits diff --git a/.github/workflows/update-v8.yml b/.github/workflows/update-v8.yml index 8e71e563896b..44137ab2fde2 100644 --- a/.github/workflows/update-v8.yml +++ b/.github/workflows/update-v8.yml @@ -30,7 +30,7 @@ jobs: ~/.npm key: ${{ runner.os }}-build-${{ env.cache-name }} - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} - name: Install @node-core/utils diff --git a/.github/workflows/update-wpt.yml b/.github/workflows/update-wpt.yml index 7b59a2f137f3..ccf81e1b3d10 100644 --- a/.github/workflows/update-wpt.yml +++ b/.github/workflows/update-wpt.yml @@ -32,7 +32,7 @@ jobs: persist-credentials: false - name: Install Node.js - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: ${{ env.NODE_VERSION }} From ead445d544db6f2125bf67cd2c9c0fae288ffe83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:35:06 +0000 Subject: [PATCH 019/195] tools: bump the eslint group in /tools/eslint with 5 updates Bumps the eslint group in /tools/eslint with 5 updates: | Package | From | To | | --- | --- | --- | | [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `8.0.6` | `8.0.7` | | [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) | `8.0.6` | `8.0.7` | | [eslint](https://github.com/eslint/eslint) | `10.11.0` | `10.13.0` | | [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) | `64.5.4` | `65.4.0` | | [globals](https://github.com/sindresorhus/globals) | `17.12.0` | `17.13.0` | Updates `@babel/core` from 8.0.6 to 8.0.7 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.7/packages/babel-core) Updates `@babel/eslint-parser` from 8.0.6 to 8.0.7 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v8.0.7/eslint/babel-eslint-parser) Updates `eslint` from 10.11.0 to 10.13.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.11.0...v10.13.0) Updates `eslint-plugin-jsdoc` from 64.5.4 to 65.4.0 - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v64.5.4...v65.4.0) Updates `globals` from 17.12.0 to 17.13.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.12.0...v17.13.0) --- updated-dependencies: - dependency-name: "@babel/core" dependency-version: 8.0.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: eslint - dependency-name: "@babel/eslint-parser" dependency-version: 8.0.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: eslint - dependency-name: eslint dependency-version: 10.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: eslint - dependency-name: eslint-plugin-jsdoc dependency-version: 65.4.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: eslint - dependency-name: globals dependency-version: 17.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: eslint ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67025 Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig --- lib/internal/modules/esm/utils.js | 1 - test/parallel/test-tls-pause.js | 1 + tools/eslint/package-lock.json | 342 +++++++++++++++++++----------- tools/eslint/package.json | 10 +- 4 files changed, 228 insertions(+), 126 deletions(-) diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index c6b101fb9070..8937b8a61298 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -106,7 +106,6 @@ function getConditionsSet(conditions) { return getDefaultConditionsSet(); } -/* eslint-disable jsdoc/valid-types */ /** * @typedef {{ * [Symbol.toStringTag]: () => 'Module' diff --git a/test/parallel/test-tls-pause.js b/test/parallel/test-tls-pause.js index f98d8d9b745c..0b4428e91671 100644 --- a/test/parallel/test-tls-pause.js +++ b/test/parallel/test-tls-pause.js @@ -64,6 +64,7 @@ server.listen(0, common.mustCall(() => { console.error('write again'); sent += bufSize; assert.ok(sent < 100 * 1024 * 1024); // max 100MB + // eslint-disable-next-line no-use-before-define return process.nextTick(send); } sent += bufSize; diff --git a/tools/eslint/package-lock.json b/tools/eslint/package-lock.json index 26247efca8b2..dd6b553cfea0 100644 --- a/tools/eslint/package-lock.json +++ b/tools/eslint/package-lock.json @@ -8,18 +8,18 @@ "name": "eslint-tools", "version": "0.0.0", "dependencies": { - "@babel/core": "^8.0.6", - "@babel/eslint-parser": "^8.0.6", + "@babel/core": "^8.0.7", + "@babel/eslint-parser": "^8.0.7", "@babel/plugin-syntax-import-defer": "^8.0.1", "@babel/plugin-syntax-import-source": "^8.0.1", "@eslint/js": "^10.0.1", "@eslint/markdown": "^8.0.3", "@stylistic/eslint-plugin": "^5.10.0", - "eslint": "^10.11.0", + "eslint": "^10.13.0", "eslint-formatter-tap": "^9.0.1", - "eslint-plugin-jsdoc": "^64.5.4", + "eslint-plugin-jsdoc": "^65.4.0", "eslint-plugin-regexp": "^3.3.1", - "globals": "^17.12.0" + "globals": "^17.13.0" } }, "node_modules/@babel/code-frame": { @@ -45,18 +45,18 @@ } }, "node_modules/@babel/core": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.6.tgz", - "integrity": "sha512-5zwYt1V4ji3mXKKRIAkohSJ29WepxNwxQi+7y7P5AcVPh1dcxnZBU2cfxHnPwS+u9+lZnXPsv30TQaF2xg+Tfg==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.7.tgz", + "integrity": "sha512-ZIsZWBG6Y1oa+8fkQVp0xeCdjCHBkXFCCIWrmg9SvejVvfcVQgKKRmGrBDWfM7rilhvnRoEuaqa5dkK7KB3pHA==", "license": "MIT", "dependencies": { "@babel/code-frame": "^8.0.6", "@babel/generator": "^8.0.6", - "@babel/helper-compilation-targets": "^8.0.6", - "@babel/helpers": "^8.0.5", - "@babel/parser": "^8.0.6", + "@babel/helper-compilation-targets": "^8.0.7", + "@babel/helpers": "^8.0.7", + "@babel/parser": "^8.0.7", "@babel/template": "^8.0.0", - "@babel/traverse": "^8.0.6", + "@babel/traverse": "^8.0.7", "@babel/types": "^8.0.6", "@types/gensync": "^1.0.5", "convert-source-map": "^2.0.0", @@ -64,8 +64,8 @@ "gensync": "^1.0.0-beta.2", "import-meta-resolve": "^4.2.0", "json5": "^2.2.3", - "obug": "^2.1.1", - "verkit": "^0.3.2" + "obug": "^3.0.0", + "verkit": "^0.5.0" }, "engines": { "node": "^22.18.0 || >=24.11.0" @@ -76,14 +76,14 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-8.0.6.tgz", - "integrity": "sha512-mXx93HLovamLnDDLQ4i48gm4HHtaoqJrbMAuJqrMfpCGJkJ5M4sXhRgKGTrxykg1P8tAN5myrJxsRZuMuTq+OQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-8.0.7.tgz", + "integrity": "sha512-ik5I+U64QuQQPohw0A1wVWPR+vTRYVfWBDLOLc1KwZQ2SKayQLD52DgoB3W/kmwc4MP1E37qh7nZuDD9xsKYcA==", "license": "MIT", "dependencies": { "eslint-scope": "^9.1.0", "eslint-visitor-keys": "^5.0.0", - "verkit": "^0.3.2" + "verkit": "^0.5.0" }, "engines": { "node": "^22.18.0 || >=24.11.0" @@ -111,16 +111,16 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.6.tgz", - "integrity": "sha512-HTrtZ4+iA0+kzdbCgvdSk1U4pEl3BvpGnlUbualCGNbrVhhQvhgBzurTUmqpWyvyHbWYWaIfdx2mOlL9LJjsew==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.7.tgz", + "integrity": "sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg==", "license": "MIT", "dependencies": { "@babel/compat-data": "^8.0.5", "@babel/helper-validator-option": "^8.0.0", "browserslist": "^4.24.0", "flru": "1.0.2", - "verkit": "^0.3.2" + "verkit": "^0.5.0" }, "engines": { "node": "^22.18.0 || >=24.11.0" @@ -148,9 +148,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", - "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.8.tgz", + "integrity": "sha512-fVM+gSuKbdv9cpmyRNh5EvsDHUZCdxQH3FdnFKSkNYHpbsn95bbrxm+ES3MVeQjcqwi2O5ur/DjLfmTQaO60ng==", "license": "MIT", "engines": { "node": "^22.18.0 || >=24.11.0" @@ -175,25 +175,25 @@ } }, "node_modules/@babel/helpers": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.5.tgz", - "integrity": "sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.8.tgz", + "integrity": "sha512-FBmhiDWs625wHg03OKiLQDeT6LOc2lWBbW97Yqs2iI1tZcucjpmYnO29NHsQ83XxKvredcvUymW/AAn2UtnakA==", "license": "MIT", "dependencies": { "@babel/template": "^8.0.0", - "@babel/types": "^8.0.5" + "@babel/types": "^8.0.8" }, "engines": { "node": "^22.18.0 || >=24.11.0" } }, "node_modules/@babel/parser": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", - "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.8.tgz", + "integrity": "sha512-Gq65JogAr0S2p0BzvfSJboyuiRnxDQApyOqHkKmkrvm+VlLK4gC3pKsiOKEdnhFBemrFbSnOu90mZZWDpiJnmQ==", "license": "MIT", "dependencies": { - "@babel/types": "^8.0.6" + "@babel/types": "^8.0.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -247,30 +247,30 @@ } }, "node_modules/@babel/traverse": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.6.tgz", - "integrity": "sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.7.tgz", + "integrity": "sha512-bVV0brinsTWgc6ptSIoQo3GbITtl0VZAqEUJbHHcyfDPEu+q/fEvlYEa+6H85HoK8IdksKBFtKJbJFZ6Kff+zg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^8.0.6", "@babel/generator": "^8.0.6", "@babel/helper-globals": "^8.0.6", - "@babel/parser": "^8.0.6", + "@babel/parser": "^8.0.7", "@babel/template": "^8.0.0", "@babel/types": "^8.0.6", - "obug": "^2.1.1" + "obug": "^3.0.0" }, "engines": { "node": "^22.18.0 || >=24.11.0" } }, "node_modules/@babel/types": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", - "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.8.tgz", + "integrity": "sha512-iXSSZAKQWmaeAwGtFw10v9fg5hUiUs0L+w+Z2K11kym3pDNK+SF0VcAQPsEFvWcTSZ7LMgClMXDBvTbH0IlICg==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-string-parser": "^8.0.8", "@babel/helper-validator-identifier": "^8.0.6" }, "engines": { @@ -299,17 +299,95 @@ "keyv": "^5.6.0" } }, + "node_modules/@es-joy/escodegen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/escodegen/-/escodegen-4.2.0.tgz", + "integrity": "sha512-jWqqS3StW6vGwJeT8yu6alS4nipOBi3kD/pR63+9hG2NfGafCJ4viDME15mVf+fyupyptcUJDXfv63gFF5Gzfg==", + "license": "BSD-2-Clause", + "dependencies": { + "@es-joy/estraverse": "^7.1.1", + "@es-joy/jsdoccomment": "^0.62.0", + "esprima": "^4.0.1", + "esutils": "^2.0.3", + "optionator": "^0.9.4" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.7.6" + } + }, + "node_modules/@es-joy/escodegen/node_modules/@es-joy/jsdoccomment": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.62.0.tgz", + "integrity": "sha512-yWi6sm7INEwnfS7IJvE0dU+RTrwzLPFcY7e7eGpu/l5Q9lWfQ2ROwZ0qVnc242jw2TUPsfHX3XMIISkGBv57RQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.44.1", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~5.9.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/@es-joy/escodegen/node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@es-joy/escodegen/node_modules/jsdoc-type-pratt-parser": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-5.9.2.tgz", + "integrity": "sha512-TYzkACp/wPvDJLRY7qpHXtrhgwoAaojIOnLaaVNi+AbPU2u1kkjfKd9hXXTq0qSAGsyYXvwUXt99h9I5iCmjjw==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@es-joy/estraverse": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@es-joy/estraverse/-/estraverse-7.1.1.tgz", + "integrity": "sha512-h2d737iS0LFAqypZOmXizu54qAbHofI/RpD30Zom03O6BCXKHuFevWKPcGarZbGq82h7qSBb5G6kLdhTMO+caw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.98.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.98.0.tgz", - "integrity": "sha512-QS+b7I2aaSM+gZfDz1b9E4B367JJ2qTjFXRKF289iKkh9sYdZ21Q5MiF/Duzouy3J0C4VhM3I/8f2F4+zT8gYQ==", + "version": "0.100.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.100.0.tgz", + "integrity": "sha512-QuIT5kMg3XWN22Wa1rcXmR/GkfSg6Ygs/ikMiHihdSpu+o3O59LygkFV9srKa5xHplBFwNd5ZCo3bywL4k7qMQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.9", - "@typescript-eslint/types": "^8.70.0", + "@typescript-eslint/types": "^8.72.0", "comment-parser": "1.4.9", "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~9.2.2" + "jsdoc-type-pratt-parser": "~9.3.0" + }, + "engines": { + "node": "^22.22.2 || >=24.15.0" + } + }, + "node_modules/@es-joy/jsdoccomment/node_modules/jsdoc-type-pratt-parser": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-9.3.0.tgz", + "integrity": "sha512-JZ8lKMCZXDlM1jAjAK5tTmtFJtgv6iFQQfjE3Fza6jBRACUueaEXLY17n1u05JO1lcPkNxgP+ddEDtiPsk5ktg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.9" }, "engines": { "node": "^22.22.2 || >=24.15.0" @@ -715,13 +793,13 @@ "license": "MIT" }, "node_modules/@typescript-eslint/project-service": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", - "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.0.tgz", + "integrity": "sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.70.0", - "@typescript-eslint/types": "^8.70.0", + "@typescript-eslint/tsconfig-utils": "^8.72.0", + "@typescript-eslint/types": "^8.72.0", "debug": "^4.4.3" }, "engines": { @@ -736,13 +814,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", - "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.0.tgz", + "integrity": "sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0" + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -753,9 +831,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", - "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.0.tgz", + "integrity": "sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -769,9 +847,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", - "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.0.tgz", + "integrity": "sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -782,15 +860,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", - "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.0.tgz", + "integrity": "sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.70.0", - "@typescript-eslint/tsconfig-utils": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/visitor-keys": "8.70.0", + "@typescript-eslint/project-service": "8.72.0", + "@typescript-eslint/tsconfig-utils": "8.72.0", + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -809,15 +887,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", - "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.0.tgz", + "integrity": "sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.70.0", - "@typescript-eslint/types": "8.70.0", - "@typescript-eslint/typescript-estree": "8.70.0" + "@typescript-eslint/scope-manager": "8.72.0", + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/typescript-estree": "8.72.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -832,12 +910,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.70.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", - "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.0.tgz", + "integrity": "sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/types": "8.72.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -910,9 +988,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.11.26", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", - "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "version": "2.11.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.33.tgz", + "integrity": "sha512-yiVKDX+qCMRKBE084lXvq9EcwFUhKZ2nU8TK1EQQuOgPNR/lmbGxXOeDdlGUqC2Zz11Cs9rk1d/8NfgBmlkt0w==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -934,9 +1012,9 @@ } }, "node_modules/browserslist": { - "version": "4.29.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", - "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.4.tgz", + "integrity": "sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==", "funding": [ { "type": "opencollective", @@ -953,11 +1031,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.11.26", - "caniuse-lite": "^1.0.30001813", - "electron-to-chromium": "^1.5.439", - "node-releases": "^2.0.57", - "update-browserslist-db": "^1.3.3" + "baseline-browser-mapping": "^2.11.29", + "caniuse-lite": "^1.0.30001816", + "electron-to-chromium": "^1.5.459", + "node-releases": "^2.0.59", + "update-browserslist-db": "^1.3.4" }, "bin": { "browserslist": "cli.js" @@ -980,9 +1058,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001814", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz", - "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==", + "version": "1.0.30001819", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001819.tgz", + "integrity": "sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==", "funding": [ { "type": "opencollective", @@ -1116,9 +1194,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.443", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.443.tgz", - "integrity": "sha512-TDJG36L9A3CWWwZ97HKaE+Iz1sW80pNp6sAU0owzCIV3Zc9eVyRqZK8Vz6NKuMIU8WjP0pIkoqCa0VRf/yFD0Q==", + "version": "1.5.464", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.464.tgz", + "integrity": "sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==", "license": "ISC" }, "node_modules/empathic": { @@ -1152,9 +1230,9 @@ } }, "node_modules/eslint": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", - "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", + "version": "10.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz", + "integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==", "license": "MIT", "workspaces": [ "packages/*" @@ -1222,14 +1300,15 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "64.5.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.4.tgz", - "integrity": "sha512-xfRhXPSSWT612muJ6XTvxuVJ8zYHv99qNgBqPVqF28aSqoy4s4XbSkrWXnYA2hl5ejC02yEpk6fqkj3BmJ6Xbg==", + "version": "65.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.4.0.tgz", + "integrity": "sha512-bRiU6LfCFmLL7GuePKvA6ZIcXLonhZGCg2TgQLmkcExeVdAirQp2Hsf3xeSUTmlKwAAsREVEOLbZYD8F7oA69g==", "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.98.0", + "@es-joy/escodegen": "^4.2.0", + "@es-joy/jsdoccomment": "~0.100.0", "@es-joy/resolve.exports": "1.2.0", - "@typescript-eslint/utils": "^8.70.0", + "@typescript-eslint/utils": "^8.72.0", "are-docs-informative": "^0.1.1", "comment-parser": "1.4.9", "debug": "^4.4.3", @@ -1382,6 +1461,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -1559,9 +1651,9 @@ } }, "node_modules/globals": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", - "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", "license": "MIT", "engines": { "node": ">=18" @@ -2685,9 +2777,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.57", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", - "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "version": "2.0.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.60.tgz", + "integrity": "sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==", "license": "MIT", "engines": { "node": ">=18" @@ -2700,9 +2792,9 @@ "license": "MIT" }, "node_modules/obug": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", - "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" @@ -2930,6 +3022,16 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", @@ -3098,9 +3200,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", - "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "funding": [ { "type": "opencollective", @@ -3137,9 +3239,9 @@ } }, "node_modules/verkit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", - "integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz", + "integrity": "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==", "license": "MIT", "engines": { "node": ">=18.12.0" diff --git a/tools/eslint/package.json b/tools/eslint/package.json index 70fb0a1825fe..47a99e4fec49 100644 --- a/tools/eslint/package.json +++ b/tools/eslint/package.json @@ -3,17 +3,17 @@ "version": "0.0.0", "private": true, "dependencies": { - "@babel/core": "^8.0.6", - "@babel/eslint-parser": "^8.0.6", + "@babel/core": "^8.0.7", + "@babel/eslint-parser": "^8.0.7", "@babel/plugin-syntax-import-defer": "^8.0.1", "@babel/plugin-syntax-import-source": "^8.0.1", "@eslint/js": "^10.0.1", "@eslint/markdown": "^8.0.3", "@stylistic/eslint-plugin": "^5.10.0", - "eslint": "^10.11.0", + "eslint": "^10.13.0", "eslint-formatter-tap": "^9.0.1", - "eslint-plugin-jsdoc": "^64.5.4", + "eslint-plugin-jsdoc": "^65.4.0", "eslint-plugin-regexp": "^3.3.1", - "globals": "^17.12.0" + "globals": "^17.13.0" } } From 5889278d221f8088fae1fef1c38ead42bd9b16de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:35:19 +0000 Subject: [PATCH 020/195] meta: bump actions/setup-python from 7.0.0 to 7.1.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 7.0.0 to 7.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/5fda3b95a4ea91299a34e894583c3862153e4b97...6d4fd22b20465ccc0f7354476588e8df7c9ca0e0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67021 Reviewed-By: Antoine du Hamel Reviewed-By: Christian Clauss --- .github/workflows/build-tarball.yml | 4 ++-- .github/workflows/coverage-linux-without-intl.yml | 2 +- .github/workflows/coverage-linux.yml | 2 +- .github/workflows/coverage-windows.yml | 2 +- .github/workflows/daily-wpt-fyi.yml | 2 +- .github/workflows/linters.yml | 8 ++++---- .github/workflows/stress-test.yml | 2 +- .github/workflows/test-internet.yml | 2 +- .github/workflows/test-linux-perfetto.yml | 2 +- .github/workflows/test-linux-quic.yml | 2 +- .github/workflows/test-linux.yml | 2 +- .github/workflows/test-macos.yml | 2 +- .github/workflows/tools.yml | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml index ecf390917e8f..009b753f4faf 100644 --- a/.github/workflows/build-tarball.yml +++ b/.github/workflows/build-tarball.yml @@ -80,7 +80,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true @@ -109,7 +109,7 @@ jobs: with: clang-version: ${{ env.CLANG_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml index 4ab6975305b9..889500fd43d6 100644 --- a/.github/workflows/coverage-linux-without-intl.yml +++ b/.github/workflows/coverage-linux-without-intl.yml @@ -58,7 +58,7 @@ jobs: with: clang-version: ${{ env.CLANG_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index a642c0fada06..5b8c91973aa1 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -58,7 +58,7 @@ jobs: with: clang-version: ${{ env.CLANG_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/coverage-windows.yml b/.github/workflows/coverage-windows.yml index a489a2cde2a2..fe2f2bcc6f75 100644 --- a/.github/workflows/coverage-windows.yml +++ b/.github/workflows/coverage-windows.yml @@ -76,7 +76,7 @@ jobs: with: persist-credentials: false - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/daily-wpt-fyi.yml b/.github/workflows/daily-wpt-fyi.yml index d8ea4588fc4e..9b7916e17d12 100644 --- a/.github/workflows/daily-wpt-fyi.yml +++ b/.github/workflows/daily-wpt-fyi.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-24.04-arm steps: - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 3f7e254b69f4..2d959d6bda7f 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -52,7 +52,7 @@ jobs: !/deps/v8/ sparse-checkout-cone-mode: false - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true @@ -72,7 +72,7 @@ jobs: with: node-version: ${{ env.NODE_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true @@ -178,7 +178,7 @@ jobs: *.py sparse-checkout-cone-mode: false - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true @@ -200,7 +200,7 @@ jobs: *.yaml sparse-checkout-cone-mode: false - name: Use Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/stress-test.yml b/.github/workflows/stress-test.yml index f715815afc40..ac18a17fd0c7 100644 --- a/.github/workflows/stress-test.yml +++ b/.github/workflows/stress-test.yml @@ -67,7 +67,7 @@ jobs: if: runner.os == 'macOS' run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/test-internet.yml b/.github/workflows/test-internet.yml index fc569f4a53e5..978b163d9709 100644 --- a/.github/workflows/test-internet.yml +++ b/.github/workflows/test-internet.yml @@ -63,7 +63,7 @@ jobs: with: clang-version: ${{ env.CLANG_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/test-linux-perfetto.yml b/.github/workflows/test-linux-perfetto.yml index edc7e5c3a772..1a8e3ca4daf7 100644 --- a/.github/workflows/test-linux-perfetto.yml +++ b/.github/workflows/test-linux-perfetto.yml @@ -50,7 +50,7 @@ jobs: rustup override set "$RUSTC_VERSION" rustup --version - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/test-linux-quic.yml b/.github/workflows/test-linux-quic.yml index 08c10537ecd0..be18b278b785 100644 --- a/.github/workflows/test-linux-quic.yml +++ b/.github/workflows/test-linux-quic.yml @@ -61,7 +61,7 @@ jobs: rustup override set "$RUSTC_VERSION" rustup --version - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 8f02723bb64e..abfe41254d94 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -73,7 +73,7 @@ jobs: rustup override set "$RUSTC_VERSION" rustup --version - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index c64cb77241bb..a2651522e5ca 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -90,7 +90,7 @@ jobs: persist-credentials: false path: node - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index ec996d6f5db4..6a64182d2def 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -346,7 +346,7 @@ jobs: persist-credentials: false - name: Set up Python ${{ env.PYTHON_VERSION }} if: matrix.requires-python && (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id) && steps.check-pr.outputs.hasNonBotCommits != 'true' - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: ${{ env.PYTHON_VERSION }} allow-prereleases: true From 2f2d9c8d973458b13b6061edad26ef886b09efa0 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 28 Oct 2026 18:58:03 +0100 Subject: [PATCH 021/195] tools: fix number comparison if Nix diff viewer Signed-off-by: Antoine du Hamel PR-URL: https://github.com/nodejs/node/pull/66904 Reviewed-By: Filip Skokan --- .github/workflows/nix-changes-comment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nix-changes-comment.yml b/.github/workflows/nix-changes-comment.yml index 1eccc6a710dc..371ed669d1f1 100644 --- a/.github/workflows/nix-changes-comment.yml +++ b/.github/workflows/nix-changes-comment.yml @@ -54,7 +54,7 @@ jobs: AFTER=$(wc -l < "requisites-${PLATFORM}-after.list") TOTAL_AFTER=$(($TOTAL_AFTER + $AFTER)) DIFF="_no changes_" - [ "$BEFORE" = "$AFTER" ] || DIFF="**$([ "$AFTER" < "$BEFORE" ] || echo '+')$(($AFTER - $BEFORE))**" + [ "$BEFORE" = "$AFTER" ] || DIFF="**$([ "$AFTER" -lt "$BEFORE" ] || echo '+')$(($AFTER - $BEFORE))**" CHANGED=$(git diff --no-index "requisites-${PLATFORM}-before.list" "requisites-${PLATFORM}-after.list" | grep -c '^+[^+]') TOTAL_CHANGED=$(($TOTAL_CHANGED + $CHANGED)) PERCENT=100 @@ -68,7 +68,7 @@ jobs: done DIFF="_no changes_" - [ "$TOTAL_BEFORE" = "$TOTAL_AFTER" ] || DIFF="**$([ "$TOTAL_AFTER" < "$TOTAL_BEFORE" ] || echo '+')$(($TOTAL_AFTER - $TOTAL_BEFORE))**" + [ "$TOTAL_BEFORE" = "$TOTAL_AFTER" ] || DIFF="**$([ "$TOTAL_AFTER" -lt "$TOTAL_BEFORE" ] || echo '+')$(($TOTAL_AFTER - $TOTAL_BEFORE))**" PERCENT=100 [ "$TOTAL_CHANGED" = "$TOTAL_AFTER" ] || { PERMILLE=$((1000 * $TOTAL_CHANGED / $TOTAL_AFTER)) From 8cbc539c379120f8143ac8aa190a28fb3cb4c617 Mon Sep 17 00:00:00 2001 From: Harjoth Khara Date: Wed, 28 Oct 2026 12:34:49 -0700 Subject: [PATCH 022/195] net,tls: fix session reuse with provided sockets Preserve literal IP hosts on net.Socket before connection. This keeps wrapped TLS sessions bound to the correct peer when tls.connect() uses a provided socket. Fixes: https://github.com/nodejs/node/issues/64402 Signed-off-by: harjoth Assisted-by: Codex PR-URL: https://github.com/nodejs/node/pull/64430 Reviewed-By: Tim Perry --- lib/net.js | 3 +- .../test-tls-session-reuse-provided-socket.js | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-tls-session-reuse-provided-socket.js diff --git a/lib/net.js b/lib/net.js index 5d20ea3ff450..ae7cca8ed6e6 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1887,6 +1887,8 @@ function lookupAndConnect(self, options) { // If host is an IP, skip performing a lookup const addressType = isIP(host); + // Retain the host before the IP fast path for TLS session identity binding. + self._host = host; if (addressType) { // An adopted BoundSocket needs no lookup: issue connect(2) in-tick so the // kernel-resolved concrete source address is observable synchronously. @@ -1928,7 +1930,6 @@ function lookupAndConnect(self, options) { debug('connect: find host', host); debug('connect: dns options', dnsopts); - self._host = host; const lookup = options.lookup || dns.lookup; if (dnsopts.family !== 4 && dnsopts.family !== 6 && !localAddress && autoSelectFamily) { diff --git a/test/parallel/test-tls-session-reuse-provided-socket.js b/test/parallel/test-tls-session-reuse-provided-socket.js new file mode 100644 index 000000000000..e23d4424463d --- /dev/null +++ b/test/parallel/test-tls-session-reuse-provided-socket.js @@ -0,0 +1,99 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const { hasMultiLocalhost } = require('../common/net'); +const net = require('net'); +const tls = require('tls'); +const { once } = require('events'); + +async function connectWithSession( + socket, session, maxVersion, expectedReuse = true, servername, +) { + const tlsSocket = tls.connect({ + socket, + session, + servername, + maxVersion, + rejectUnauthorized: false, + }); + const close = once(tlsSocket, 'close'); + tlsSocket.resume(); + await once(tlsSocket, 'secureConnect'); + assert.strictEqual(tlsSocket.isSessionReused(), expectedReuse); + await close; +} + +async function connectAndCaptureSession( + port, maxVersion, host = '127.0.0.1', lookup, +) { + const socket = tls.connect({ + host, + lookup, + port, + maxVersion, + rejectUnauthorized: false, + }); + const close = once(socket, 'close'); + const sessionPromise = once(socket, 'session').then(([session]) => session); + socket.resume(); + await once(socket, 'secureConnect'); + assert.strictEqual(socket.isSessionReused(), false); + const session = await sessionPromise; + assert(Buffer.isBuffer(session)); + await close; + return session; +} + +async function testVersion(maxVersion) { + const server = tls.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + minVersion: maxVersion, + maxVersion, + }, (socket) => socket.end('ok')); + + server.listen(0, '0.0.0.0'); + await once(server, 'listening'); + const { port } = server.address(); + const session = await connectAndCaptureSession(port, maxVersion); + + let socket = net.connect({ host: '127.0.0.1', port }); + await once(socket, 'connect'); + await connectWithSession(socket, session, maxVersion); + + socket = net.connect({ host: '127.0.0.1', port }); + await connectWithSession(socket, session, maxVersion); + + if (hasMultiLocalhost()) { + socket = net.connect({ host: '127.0.0.2', port }); + await connectWithSession(socket, session, maxVersion, false); + } + + const lookup = (_hostname, _options, callback) => { + if (_options.all) { + callback(null, [{ address: '127.0.0.1', family: 4 }]); + } else { + callback(null, '127.0.0.1', 4); + } + }; + const hostnameSession = await connectAndCaptureSession( + port, maxVersion, 'localhost', lookup); + socket = net.connect({ host: 'localhost', port, lookup }); + await connectWithSession(socket, hostnameSession, maxVersion); + + socket = net.connect({ host: '127.0.0.1', port }); + await connectWithSession(socket, session, maxVersion, false, 'localhost'); + + server.close(); + await once(server, 'close'); +} + +(async function() { + await testVersion('TLSv1.2'); + await testVersion('TLSv1.3'); +})().then(common.mustCall()); From 3f639bd72c99c31bcf914464f616f556f98ed125 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:53:22 +0000 Subject: [PATCH 023/195] meta: bump ossf/scorecard-action from 2.4.4 to 2.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.4 to 2.5.0. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/2d1146689b8cda280b9bc96326124645441f03bc...fa7117db83c52d787ae5a89f350999701505d78e) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] PR-URL: https://github.com/nodejs/node/pull/67020 Reviewed-By: Ulises Gascón Reviewed-By: Luigi Pinca --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 994538ab4cde..16f1846a81a3 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -46,7 +46,7 @@ jobs: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 with: results_file: results.sarif results_format: sarif From 12a857a938192f8f1915612d7051636bd20ae176 Mon Sep 17 00:00:00 2001 From: Donghoon Kang Date: Thu, 29 Oct 2026 08:17:28 +0900 Subject: [PATCH 024/195] doc: fix sqlite tagged template examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: HoonDongKang PR-URL: https://github.com/nodejs/node/pull/66920 Reviewed-By: Trivikram Kamat Reviewed-By: Guilherme Araújo --- doc/api/sqlite.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 2df8dbcd6e7b..ccfc3cd4427d 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -791,13 +791,13 @@ statement for future use. > literals, where it performs string interpolation. > > ```js -> // This a safe example of binding a parameter to a tagged statement. +> // This is a safe example of binding a parameter to a tagged statement. > sqlTagStore.run`INSERT INTO t1 (id) VALUES (${id})`; > > // This is an *unsafe* example of an untagged template string. > // `id` is interpolated into the query text as a string. > // This can lead to SQL injection and data corruption. -> db.run(`INSERT INTO t1 (id) VALUES (${id})`); +> db.exec(`INSERT INTO t1 (id) VALUES (${id})`); > ``` The tag store will match a statement from the cache if the query strings From 5bc98ec49da1b5f42f611bccd5ffa509853731b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Tue, 20 Oct 2026 22:49:34 -0300 Subject: [PATCH 025/195] sqlite: track statements in the Statement constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Statement::Close() untracks itself, so the constructor should track itself too, like Session does, instead of relying on every caller. Signed-off-by: Guilherme Araújo PR-URL: https://github.com/nodejs/node/pull/66912 Reviewed-By: Trivikram Kamat --- src/node_sqlite.cc | 7 +++++-- src/node_sqlite.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 36aab3f3a421..07979acd3df1 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -1769,6 +1769,10 @@ void Database::FinalizeStatements() { statements_.clear(); } +void Database::TrackStatement(Statement* statement) { + statements_.insert(statement); +} + void Database::UntrackStatement(Statement* statement) { statements_.erase(statement); } @@ -2182,7 +2186,6 @@ void Database::Prepare(const FunctionCallbackInfo& args) { if (!stmt) { return; } - db->statements_.insert(stmt.get()); if (return_arrays.has_value()) { stmt->return_arrays_ = return_arrays.value(); @@ -3491,6 +3494,7 @@ Statement::Statement(Environment* env, BaseObjectPtr db, StatementPtr stmt) : BaseObject(env, object), db_(std::move(db)), statement_(std::move(stmt)) { + db_->TrackStatement(this); MakeWeak(); use_big_ints_ = db_->use_big_ints(); return_arrays_ = db_->return_arrays(); @@ -4660,7 +4664,6 @@ BaseObjectPtr SQLTagStore::PrepareStatement( return BaseObjectPtr(); } - session->database_->statements_.insert(stmt_obj.get()); session->sql_tags_.Put(sql, stmt_obj); stmt = stmt_obj; } diff --git a/src/node_sqlite.h b/src/node_sqlite.h index c4d7e55547d9..2adb95f62dd5 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -262,6 +262,7 @@ class Database : public BaseObject { void RemoveBackup(BackupJob* backup); void AddBackup(BackupJob* backup); void FinalizeBackups(); + void TrackStatement(Statement* statement); void UntrackStatement(Statement* statement); bool IsOpen(); // SQL functions are one of several paths by which SQLite can invoke JS. From 2ec332989bbfd6418d351131492895fbb8014dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Tue, 20 Oct 2026 22:51:22 -0300 Subject: [PATCH 026/195] sqlite: use DeleteFnPtr for all SQLite handles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the custom deleter structs and OnScopeLeave cleanups with DeleteFnPtr aliases, and pass session ownership to Session::Create. Signed-off-by: Guilherme Araújo PR-URL: https://github.com/nodejs/node/pull/66912 Reviewed-By: Trivikram Kamat --- src/node_sqlite.cc | 46 ++++++++++++++++++---------------------------- src/node_sqlite.h | 25 +++++++++++++++---------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 07979acd3df1..b10044a7b15e 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -743,9 +743,7 @@ class BackupJob : public ThreadPoolWork { } void Cleanup() { - if (backup_) { - sqlite3_backup_finish(backup_.release()); - } + backup_.reset(); if (dest_) { backup_status_ = sqlite3_errcode(dest_.get()); @@ -786,14 +784,8 @@ class BackupJob : public ThreadPoolWork { BaseObjectPtr source_; Global resolver_; Global progressFunc_; - struct Sqlite3Deleter { - void operator()(sqlite3* db) const { sqlite3_close_v2(db); } - }; - struct BackupDeleter { - void operator()(sqlite3_backup* b) const { sqlite3_backup_finish(b); } - }; - std::unique_ptr dest_; - std::unique_ptr backup_; + ConnectionPtr dest_; + BackupPtr backup_; int pages_; int backup_status_ = SQLITE_OK; std::string source_db_; @@ -2101,6 +2093,8 @@ void Database::Close(const FunctionCallbackInfo& args) { env, db->IsInCallback(), "database cannot be closed while in a callback"); db->FinalizeStatements(); db->DeleteSessions(); + // Close through the raw handle instead of reset() so that a failed close + // keeps the handle, which the error below is read from. int r = sqlite3_close_v2(db->connection_.get()); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); db->connection_.release(); @@ -2892,26 +2886,21 @@ void Database::CreateSession(const FunctionCallbackInfo& args) { // getter, which may have closed the database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); - sqlite3_session* pSession; - int r = - sqlite3session_create(db->connection_.get(), db_name.c_str(), &pSession); + sqlite3_session* raw_session; + int r = sqlite3session_create( + db->connection_.get(), db_name.c_str(), &raw_session); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); - bool wrapper_owns_session = false; - auto delete_session_on_failure = OnScopeLeave([&]() { - if (!wrapper_owns_session) { - sqlite3session_delete(pSession); - } - }); + SessionPtr pSession(raw_session); - r = sqlite3session_attach(pSession, table == "" ? nullptr : table.c_str()); + r = sqlite3session_attach(pSession.get(), + table == "" ? nullptr : table.c_str()); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); BaseObjectPtr session = - Session::Create(env, BaseObjectPtr(db), pSession); + Session::Create(env, BaseObjectPtr(db), std::move(pSession)); if (!session) { return; } - wrapper_owns_session = true; args.GetReturnValue().Set(session->object()); } @@ -4898,9 +4887,9 @@ void StatementIterator::Return(const FunctionCallbackInfo& args) { Session::Session(Environment* env, Local object, BaseObjectPtr database, - sqlite3_session* session) + SessionPtr session) : BaseObject(env, object), - session_(std::unique_ptr(session)), + session_(std::move(session)), database_(std::move(database)) { database_->sessions_.insert(this); MakeWeak(); @@ -4912,7 +4901,7 @@ Session::~Session() { BaseObjectPtr Session::Create(Environment* env, BaseObjectPtr database, - sqlite3_session* session) { + SessionPtr session) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() @@ -4921,7 +4910,8 @@ BaseObjectPtr Session::Create(Environment* env, return nullptr; } - return MakeBaseObject(env, obj, std::move(database), session); + return MakeBaseObject( + env, obj, std::move(database), std::move(session)); } Local Session::GetConstructorTemplate(Environment* env) { @@ -4970,7 +4960,7 @@ void Session::Changeset(const FunctionCallbackInfo& args) { CHECK_ERROR_OR_THROW( env->isolate(), session->database_.get(), r, SQLITE_OK, void()); - auto freeChangeset = OnScopeLeave([&] { sqlite3_free(pChangeset); }); + DeleteFnPtr changeset(pChangeset); Local buffer = ArrayBuffer::New(env->isolate(), nChangeset); if (nChangeset > 0) { diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 2adb95f62dd5..2bba70c3bbae 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -173,7 +173,18 @@ inline void FinalizeStatement(sqlite3_stmt* stmt) { sqlite3_finalize(stmt); } +inline void CloseConnection(sqlite3* db) { + sqlite3_close_v2(db); +} + +inline void FinishBackup(sqlite3_backup* backup) { + sqlite3_backup_finish(backup); +} + using StatementPtr = DeleteFnPtr; +using ConnectionPtr = DeleteFnPtr; +using BackupPtr = DeleteFnPtr; +using SessionPtr = DeleteFnPtr; class StatementExecutionHelper { public: @@ -338,10 +349,7 @@ class Database : public BaseObject { DatabaseOpenConfiguration open_config_; bool allow_load_extension_; bool enable_load_extension_; - struct ConnectionDeleter { - void operator()(sqlite3* db) const { sqlite3_close_v2(db); } - }; - std::unique_ptr connection_; + ConnectionPtr connection_; bool ignore_next_sqlite_error_; int callback_depth_ = 0; int destructor_depth_ = 0; @@ -465,7 +473,7 @@ class Session : public BaseObject { Session(Environment* env, v8::Local object, BaseObjectPtr database, - sqlite3_session* session); + SessionPtr session); ~Session() override; template static void Changeset(const v8::FunctionCallbackInfo& args); @@ -475,7 +483,7 @@ class Session : public BaseObject { Environment* env); static BaseObjectPtr Create(Environment* env, BaseObjectPtr database, - sqlite3_session* session); + SessionPtr session); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Session) @@ -483,10 +491,7 @@ class Session : public BaseObject { private: void Delete(); - struct SessionDeleter { - void operator()(sqlite3_session* s) const { sqlite3session_delete(s); } - }; - std::unique_ptr session_; + SessionPtr session_; BaseObjectPtr database_; // The Parent Database bool is_generating_changeset_ = false; From c38a50365c2c97bc643c23dd037742ded3ec61e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Tue, 20 Oct 2026 22:52:14 -0300 Subject: [PATCH 027/195] sqlite: settle BackupJob through a single exit point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route every exit through Settle(), which finalizes, settles the promise and deletes the job once. This also frees the job when building the progress object fails. Signed-off-by: Guilherme Araújo PR-URL: https://github.com/nodejs/node/pull/66912 Reviewed-By: Trivikram Kamat --- src/node_sqlite.cc | 52 ++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index b10044a7b15e..71cc49c09fee 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -645,7 +645,7 @@ class BackupJob : public ThreadPoolWork { Local resolver = Local::New(env()->isolate(), resolver_); if (backup_status_ != SQLITE_OK) { - HandleBackupError(resolver); + Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } @@ -654,7 +654,7 @@ class BackupJob : public ThreadPoolWork { source_->Connection(), source_db_.c_str())); if (!backup_) { - HandleBackupError(resolver); + Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } @@ -676,7 +676,7 @@ class BackupJob : public ThreadPoolWork { if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE || backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) { - HandleBackupError(resolver, backup_status_); + Settle(resolver, CreateSQLiteError(isolate, backup_status_), {}); return; } @@ -702,6 +702,7 @@ class BackupJob : public ThreadPoolWork { env()->remaining_pages_string(), Integer::New(env()->isolate(), remaining_pages)) .IsNothing()) { + Settle(resolver, {}, {}); return; } @@ -709,10 +710,7 @@ class BackupJob : public ThreadPoolWork { TryCatch try_catch(env()->isolate()); USE(fn->Call(env()->context(), Null(env()->isolate()), 1, argv)); if (try_catch.HasCaught()) { - Local exception = try_catch.Exception(); - Finalize(); - resolver->Reject(env()->context(), exception).ToChecked(); - delete this; + Settle(resolver, try_catch.Exception(), {}); return; } } @@ -723,15 +721,11 @@ class BackupJob : public ThreadPoolWork { } if (backup_status_ != SQLITE_DONE) { - HandleBackupError(resolver); + Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } - Finalize(); - resolver - ->Resolve(env()->context(), Integer::New(env()->isolate(), total_pages)) - .ToChecked(); - delete this; + Settle(resolver, {}, Integer::New(isolate, total_pages)); } void Finalize() { @@ -752,29 +746,19 @@ class BackupJob : public ThreadPoolWork { } private: - void HandleBackupError(Local resolver) { - Local e; - if (!CreateSQLiteError(env()->isolate(), dest_.get()).ToLocal(&e)) { - Finalize(); - delete this; - return; - } - + // The single exit point of a backup. Rejects with `error` if it is set, + // otherwise resolves with `value` if it is set. Neither being set means an + // exception is already pending. Deletes the job. + void Settle(Local resolver, + MaybeLocal error, + Local value) { Finalize(); - resolver->Reject(env()->context(), e).ToChecked(); - delete this; - } - - void HandleBackupError(Local resolver, int errcode) { - Local e; - if (!CreateSQLiteError(env()->isolate(), errcode).ToLocal(&e)) { - Finalize(); - delete this; - return; + Local e; + if (error.ToLocal(&e)) { + resolver->Reject(env()->context(), e).ToChecked(); + } else if (!value.IsEmpty()) { + resolver->Resolve(env()->context(), value).ToChecked(); } - - Finalize(); - resolver->Reject(env()->context(), e).ToChecked(); delete this; } From 9c381d00b31ad91275c7633b1b016359af8e3f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Tue, 20 Oct 2026 22:53:03 -0300 Subject: [PATCH 028/195] sqlite: cache the SQLTagStore constructor template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store the template on the environment like the other sqlite classes, so stores no longer get a fresh template and prototype each time. Signed-off-by: Guilherme Araújo PR-URL: https://github.com/nodejs/node/pull/66912 Reviewed-By: Trivikram Kamat --- src/env_properties.h | 1 + src/node_sqlite.cc | 39 ++++++++++++----------- test/parallel/test-sqlite-template-tag.js | 6 ++++ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/env_properties.h b/src/env_properties.h index c4e3bc5c72a0..9f634ec13f92 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -457,6 +457,7 @@ V(sqlite_statement_sync_constructor_template, v8::FunctionTemplate) \ V(sqlite_statement_sync_iterator_constructor_template, v8::FunctionTemplate) \ V(sqlite_session_constructor_template, v8::FunctionTemplate) \ + V(sqlite_tag_store_constructor_template, v8::FunctionTemplate) \ V(srv_record_template, v8::DictionaryTemplate) \ V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \ V(tcp_constructor_template, v8::FunctionTemplate) \ diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 71cc49c09fee..2bf67b97689f 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -4353,24 +4353,27 @@ static inline void SetSideEffectFreeGetter( SQLTagStore::~SQLTagStore() {} Local SQLTagStore::GetConstructorTemplate(Environment* env) { - Isolate* isolate = env->isolate(); - Local tmpl = - NewFunctionTemplate(isolate, IllegalConstructor); - tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SQLTagStore")); - tmpl->InstanceTemplate()->SetInternalFieldCount( - SQLTagStore::kInternalFieldCount); - SetProtoMethod(isolate, tmpl, "get", Get); - SetProtoMethod(isolate, tmpl, "all", All); - SetProtoMethod(isolate, tmpl, "iterate", Iterate); - SetProtoMethod(isolate, tmpl, "run", Run); - SetProtoMethod(isolate, tmpl, "clear", Clear); - SetSideEffectFreeGetter(isolate, - tmpl, - FIXED_ONE_BYTE_STRING(isolate, "capacity"), - CapacityGetter); - SetSideEffectFreeGetter( - isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "db"), DatabaseGetter); - SetSideEffectFreeGetter(isolate, tmpl, env->size_string(), SizeGetter); + Local tmpl = env->sqlite_tag_store_constructor_template(); + if (tmpl.IsEmpty()) { + Isolate* isolate = env->isolate(); + tmpl = NewFunctionTemplate(isolate, IllegalConstructor); + tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SQLTagStore")); + tmpl->InstanceTemplate()->SetInternalFieldCount( + SQLTagStore::kInternalFieldCount); + SetProtoMethod(isolate, tmpl, "get", Get); + SetProtoMethod(isolate, tmpl, "all", All); + SetProtoMethod(isolate, tmpl, "iterate", Iterate); + SetProtoMethod(isolate, tmpl, "run", Run); + SetProtoMethod(isolate, tmpl, "clear", Clear); + SetSideEffectFreeGetter(isolate, + tmpl, + FIXED_ONE_BYTE_STRING(isolate, "capacity"), + CapacityGetter); + SetSideEffectFreeGetter( + isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "db"), DatabaseGetter); + SetSideEffectFreeGetter(isolate, tmpl, env->size_string(), SizeGetter); + env->set_sqlite_tag_store_constructor_template(tmpl); + } return tmpl; } diff --git a/test/parallel/test-sqlite-template-tag.js b/test/parallel/test-sqlite-template-tag.js index f7a5053a3301..5ecf055523f4 100644 --- a/test/parallel/test-sqlite-template-tag.js +++ b/test/parallel/test-sqlite-template-tag.js @@ -295,6 +295,12 @@ test('sql.db returns the associated Database instance', () => { assert.strictEqual(sql.db, db); }); +test('tag stores share a prototype', () => { + const other = new Database(':memory:').createTagStore(); + assert.strictEqual(Object.getPrototypeOf(other), Object.getPrototypeOf(sql)); + assert.strictEqual(other.constructor, sql.constructor); +}); + test('sql error messages are descriptive', () => { assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'test'})`.changes, 1); From 2e27c83dd2246181e5b1bea211696352ac54a245 Mon Sep 17 00:00:00 2001 From: Seongeun Lee Date: Thu, 29 Oct 2026 17:47:51 +0900 Subject: [PATCH 029/195] typings: share Sign and Verify handle methods Extract SignBaseHandle for the shared init and update methods. Preserve existing signatures and runtime behavior. Assisted-by: Codex Signed-off-by: leah-1ee PR-URL: https://github.com/nodejs/node/pull/66499 Reviewed-By: Tim Perry --- typings/internalBinding/crypto.d.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/typings/internalBinding/crypto.d.ts b/typings/internalBinding/crypto.d.ts index 3d5a9fceb2e6..87aeffdf230c 100644 --- a/typings/internalBinding/crypto.d.ts +++ b/typings/internalBinding/crypto.d.ts @@ -665,9 +665,12 @@ declare namespace InternalCryptoBinding { setPrivateKey(key: ByteSource): void; } - interface SignHandle { + interface SignBaseHandle { init(algorithm: string): void; update(data: ByteSource, encoding?: string): void; + } + + interface SignHandle extends SignBaseHandle { sign( ...args: [ ...key: PreparedAsymmetricKeyArgs, @@ -678,9 +681,7 @@ declare namespace InternalCryptoBinding { ): Buffer; } - interface VerifyHandle { - init(algorithm: string): void; - update(data: ByteSource, encoding?: string): void; + interface VerifyHandle extends SignBaseHandle { verify( ...args: [ ...key: PreparedAsymmetricKeyArgs, From 33d4e6c73a5fb5880274c20651c7531beb6f862f Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 29 Oct 2026 09:56:09 +0100 Subject: [PATCH 030/195] child_process: add subprocess.timedOut When the timeout option of spawn(), exec(), execFile() or fork() expires, the child is sent the kill signal and exits with that signal. Nothing told the caller that the timeout was the reason. Set subprocess.timedOut to true when the kill signal is sent because of the timeout, and add the same flag to the error passed to the exec() and execFile() callback. Fixes: https://github.com/nodejs/node/issues/51561 Refs: https://github.com/nodejs/node/pull/51608 Signed-off-by: marcopiraccini PR-URL: https://github.com/nodejs/node/pull/66639 Reviewed-By: Paolo Insogna --- doc/api/child_process.md | 30 +++++++++- lib/child_process.js | 12 +++- lib/internal/child_process.js | 1 + .../test-child-process-exec-timeout-expire.js | 1 + .../test-child-process-exec-timeout-kill.js | 4 +- ...-child-process-exec-timeout-not-expired.js | 40 ++++++++++++- ...child-process-spawn-timeout-kill-signal.js | 59 ++++++++++++++++++- 7 files changed, 139 insertions(+), 8 deletions(-) diff --git a/doc/api/child_process.md b/doc/api/child_process.md index 28729ef788ac..6ac072beedb4 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -233,7 +233,8 @@ If a `callback` function is provided, it is called with the arguments `error` will be an instance of [`Error`][]. The `error.code` property will be the exit code of the process. By convention, any exit code other than `0` indicates an error. `error.signal` will be the signal that terminated the -process. +process. `error.timedOut` will be `true` if the process was killed because +the `timeout` option expired. The `stdout` and `stderr` arguments passed to the callback will contain the stdout and stderr output of the child process. By default, Node.js will decode @@ -2316,6 +2317,32 @@ subprocess.stdout.on('data', (data) => { The `subprocess.stdout` property can be `null` or `undefined` if the child process could not be successfully spawned. +### `subprocess.timedOut` + + + +* Type: {boolean} Set to `true` when the child process is killed because the + `timeout` option expired. + +The `subprocess.timedOut` property indicates whether the `timeout` option of +[`child_process.spawn()`][], [`child_process.exec()`][], +[`child_process.execFile()`][] or [`child_process.fork()`][] expired and the +child process was sent the `killSignal` as a result. Like +[`subprocess.killed`][], it does not indicate that the child process has +terminated yet. + +```js +const { spawn } = require('node:child_process'); + +const subprocess = spawn('sleep', ['10'], { timeout: 100 }); + +subprocess.on('exit', (code, signal) => { + console.log(subprocess.timedOut); // true +}); +``` + ### `subprocess.unref()` -Type: Documentation-only +Type: Runtime The `node:_http_agent`, `node:_http_client`, `node:_http_common`, `node:_http_incoming`, `node:_http_outgoing` and `node:_http_server` modules are deprecated as they should be considered From 89db5c3e4c8f613c0c48c42c8889a7c532c2c712 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 2 Nov 2026 16:34:58 -0800 Subject: [PATCH 081/195] ffi: define function pointer property as own data property `CreateFunction` assigned `pointer` with `Object::Set`, which walks `Function.prototype`. A non-writable inherited `pointer` turned the store into a silent no-op, so the function reported the inherited value instead of its native address. Use `CreateDataProperty`. Signed-off-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Assisted-by: claude:opus-5.5 PR-URL: https://github.com/nodejs/node/pull/67170 Fixes: https://github.com/nodejs/node/issues/67169 Reviewed-By: Anna Henningsen Reviewed-By: Paolo Insogna --- src/node_ffi.cc | 2 +- test/ffi/test-ffi-dynamic-library.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/node_ffi.cc b/src/node_ffi.cc index b988120606c6..81b698a75304 100644 --- a/src/node_ffi.cc +++ b/src/node_ffi.cc @@ -364,7 +364,7 @@ MaybeLocal DynamicLibrary::CreateFunction( } ret->SetName(name_str.As()); - if (!ret->Set( + if (!ret->CreateDataProperty( context, env->pointer_string(), BigInt::NewFromUnsigned( diff --git a/test/ffi/test-ffi-dynamic-library.js b/test/ffi/test-ffi-dynamic-library.js index 62cc7e0b4d49..7dcbf175a863 100644 --- a/test/ffi/test-ffi-dynamic-library.js +++ b/test/ffi/test-ffi-dynamic-library.js @@ -92,11 +92,16 @@ test('DynamicLibrary exposes functions and symbols', () => { const lib = new ffi.DynamicLibrary(libraryPath); try { + // An inherited non-writable `pointer` must not shadow the address. + Object.defineProperty(Function.prototype, 'pointer', { + value: 1, writable: false, configurable: true, + }); const addI32 = lib.getFunction('add_i32', fixtureSymbols.add_i32); const addU64 = lib.getFunction('add_u64', { return: 'u64', arguments: ['u64', 'u64'], }); + delete Function.prototype.pointer; const addI32Ptr = lib.getSymbol('add_i32'); assert.strictEqual(addI32(20, 22), 42); From be9989a092acdbe644482e0959ac0d0711e491a4 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 2 Nov 2026 19:52:58 -0500 Subject: [PATCH 082/195] deps: patch V8 to 15.6.75.33 Refs: https://github.com/v8/v8/compare/15.6.75.25...15.6.75.33 PR-URL: https://github.com/nodejs/node/pull/67174 Reviewed-By: Chengzhong Wu Reviewed-By: Luigi Pinca Reviewed-By: Joyee Cheung --- deps/v8/include/cppgc/heap-consistency.h | 4 +- deps/v8/include/v8-version.h | 2 +- deps/v8/src/builtins/promise-any.tq | 51 +++++----- deps/v8/src/compiler/js-generic-lowering.cc | 3 - deps/v8/src/compiler/js-operator.cc | 2 +- deps/v8/src/compiler/turboshaft/pipelines.cc | 15 +++ deps/v8/src/heap/cppgc-internal/marker.h | 43 ++++----- deps/v8/src/maglev/maglev-compiler.cc | 2 + deps/v8/src/maglev/maglev-graph-builder.cc | 1 + deps/v8/src/objects/js-function.cc | 8 +- deps/v8/src/objects/map-inl.h | 5 +- deps/v8/src/objects/map-updater.cc | 8 +- deps/v8/src/objects/map.cc | 16 +++- deps/v8/src/runtime/runtime-literals.cc | 92 ++++++++++++------- .../mjsunit/compiler/regress-574339635.js | 37 ++++++++ .../mjsunit/compiler/regress-578688670.js | 56 +++++++++++ .../test/mjsunit/maglev/regress-574021176.js | 44 +++++++++ .../opt-proto-seq/regress-577944790.js | 65 +++++++++++++ .../opt-proto-seq/regress-578083257.js | 63 +++++++++++++ .../test/mjsunit/regress/regress-577318603.js | 74 +++++++++++++++ .../mjsunit/turboshaft/regress-574660146.js | 41 +++++++++ .../heap/cppgc/write-barrier-unittest.cc | 4 +- 22 files changed, 535 insertions(+), 101 deletions(-) create mode 100644 deps/v8/test/mjsunit/compiler/regress-574339635.js create mode 100644 deps/v8/test/mjsunit/compiler/regress-578688670.js create mode 100644 deps/v8/test/mjsunit/maglev/regress-574021176.js create mode 100644 deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js create mode 100644 deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js create mode 100644 deps/v8/test/mjsunit/regress/regress-577318603.js create mode 100644 deps/v8/test/mjsunit/turboshaft/regress-574660146.js diff --git a/deps/v8/include/cppgc/heap-consistency.h b/deps/v8/include/cppgc/heap-consistency.h index 23b5d909945b..31858f0a631b 100644 --- a/deps/v8/include/cppgc/heap-consistency.h +++ b/deps/v8/include/cppgc/heap-consistency.h @@ -146,8 +146,8 @@ class HeapConsistency final { } /** - * Steele-style write barrier that re-processes an object if it has already - * been processed. + * Steele-style write barrier that processes an object regardless of whether + * or not it has already been processed. * * \param params The parameters retrieved from `GetWriteBarrierType()`. * \param object The pointer to the object which must point to an object that diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index f91b6e7018f8..11e0def9233c 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 15 #define V8_MINOR_VERSION 6 #define V8_BUILD_NUMBER 75 -#define V8_PATCH_LEVEL 25 +#define V8_PATCH_LEVEL 33 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/src/builtins/promise-any.tq b/deps/v8/src/builtins/promise-any.tq index 427d31c2840b..6f3d91f19959 100644 --- a/deps/v8/src/builtins/promise-any.tq +++ b/deps/v8/src/builtins/promise-any.tq @@ -26,9 +26,7 @@ extern operator '[]' macro LoadContextElementNoCell( // together with the errors array. Since all closures for a single Promise.any // call use the same context, we need to store the indices for the individual // closures somewhere else (we put them into the identity hash field of the -// closures), and we also need to have a separate marker for when the closure -// was called already (we slap the native context onto the closure in that -// case to mark it's done). See Promise.all which uses the same approach. +// closures). transitioning macro CreatePromiseAnyRejectElementContext( implicit context: Context)(capability: PromiseCapability, nativeContext: NativeContext): PromiseAnyRejectElementContext { @@ -75,36 +73,30 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( js-implicit context: Context, receiver: JSAny, target: JSFunction)( value: JSAny): JSAny { // 1. Let F be the active function object. - - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - - // 3. If alreadyCalled.[[Value]] is true, return undefined. - - // We use the function's context as the marker to remember whether this - // reject element closure was already called. It points to the reject - // element context (which is a FunctionContext) until it was called the - // first time, in which case we make it point to the native context here - // to mark this reject element closure as done. - if (IsNativeContext(context)) deferred { - return Undefined; - } - dcheck( context.length == SmiTag( PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); const context = %RawDownCast(context); - // 4. Set alreadyCalled.[[Value]] to true. - const nativeContext = LoadNativeContext(context); - target.context = nativeContext; - // 5. Let index be F.[[Index]]. dcheck(kPropertyArrayNoHashSentinel == 0); const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable; dcheck(ChangeUint32ToWord(identityHash) < kSmiMaxValue); const index = Signed(ChangeUint32ToWord(identityHash)) - 1; + // 8. Let remainingElementsCount be F.[[RemainingElements]]. + let remainingElementsCount = *ContextSlot( + context, + PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot); + + // If all promises were already rejected, the remaining count will already be + // 0. + if (remainingElementsCount == 0) deferred { + return Undefined; + } + // 6. Let errors be F.[[Errors]]. let errorsRef:&FixedArray = ContextSlot( context, @@ -113,12 +105,6 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. - let remainingElementsCount = *ContextSlot( - context, - PromiseAnyRejectElementContextSlots:: - kPromiseAnyRejectElementRemainingSlot); - // 9. Set errors[index] to x. // The max computation below is an optimization to avoid excessive allocations @@ -138,11 +124,22 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( errors, 0, errors.length_intptr, newCapacity, PromiseHole); *errorsRef = errors; } + + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 3. If alreadyCalled.[[Value]] is true, return undefined. + // 4. Set alreadyCalled.[[Value]] to true. + if (errors.objects[index] != PromiseHole) deferred { + return Undefined; + } + + // Make sure that we never see the PromiseHole here as a result. + check(value != PromiseHole); errors.objects[index] = value; // 10. Set remainingElementsCount.[[Value]] to // remainingElementsCount.[[Value]] - 1. remainingElementsCount = remainingElementsCount - 1; + check(remainingElementsCount >= 0); *ContextSlot( context, PromiseAnyRejectElementContextSlots:: diff --git a/deps/v8/src/compiler/js-generic-lowering.cc b/deps/v8/src/compiler/js-generic-lowering.cc index 2affa53f2fba..d32bb061c04a 100644 --- a/deps/v8/src/compiler/js-generic-lowering.cc +++ b/deps/v8/src/compiler/js-generic-lowering.cc @@ -759,9 +759,6 @@ void JSGenericLowering::LowerJSGetTemplateObject(Node* node) { SharedFunctionInfoRef shared = p.shared(); TemplateObjectDescriptionRef description = p.description(); - DCHECK_EQ(node->op()->ControlInputCount(), 1); - node->RemoveInput(NodeProperties::FirstControlIndex(node)); - static_assert(JSGetTemplateObjectNode::FeedbackVectorIndex() == 0); node->InsertInput(zone(), 0, jsgraph()->ConstantNoHole(shared, broker())); node->InsertInput(zone(), 1, diff --git a/deps/v8/src/compiler/js-operator.cc b/deps/v8/src/compiler/js-operator.cc index 5aec87c5ad38..75810fa1a732 100644 --- a/deps/v8/src/compiler/js-operator.cc +++ b/deps/v8/src/compiler/js-operator.cc @@ -1614,7 +1614,7 @@ const Operator* JSOperatorBuilder::GetTemplateObject( GetTemplateObjectParameters parameters(description, shared, feedback); return zone()->New>( // -- IrOpcode::kJSGetTemplateObject, // opcode - Operator::kEliminatable, // properties + Operator::kNoDeopt | Operator::kNoThrow, // properties "JSGetTemplateObject", // name 1, 1, 1, 1, 1, 0, // counts parameters); // parameter diff --git a/deps/v8/src/compiler/turboshaft/pipelines.cc b/deps/v8/src/compiler/turboshaft/pipelines.cc index 3c773aeac12d..4aff92cb2a84 100644 --- a/deps/v8/src/compiler/turboshaft/pipelines.cc +++ b/deps/v8/src/compiler/turboshaft/pipelines.cc @@ -85,6 +85,21 @@ bool Pipeline::AllocateRegisters(const RegisterConfiguration* config, RUN_MAYBE_ABORT(DecideSpillingModePhase); RUN_MAYBE_ABORT(AssignSpillSlotsPhase); + + // The prologue grows the frame with a single stack pointer decrement, before + // the function entry stack check runs. A frame no larger than the smallest OS + // page cannot step over a guard page, so bail out rather than emit an + // unchecked stack pointer jump of arbitrary size. + // When replacing this bailout with a different strategy, consider doing the + // same in Maglev as well. + constexpr int kMaxStackSlots = 4 * KB / kSystemPointerSize; + if (data_->info()->IsOptimizing() && + data_->frame()->GetTotalFrameSlotCount() > kMaxStackSlots) { + data_->info()->AbortOptimization(BailoutReason::kFunctionTooBig); + data()->ClearRegisterComponent(); + return false; + } + RUN_MAYBE_ABORT(CommitAssignmentPhase); // TODO(chromium:725559): remove this check once diff --git a/deps/v8/src/heap/cppgc-internal/marker.h b/deps/v8/src/heap/cppgc-internal/marker.h index 84fec0712628..ad96e22f188b 100644 --- a/deps/v8/src/heap/cppgc-internal/marker.h +++ b/deps/v8/src/heap/cppgc-internal/marker.h @@ -10,6 +10,7 @@ #include "include/cppgc/heap.h" #include "include/cppgc/platform.h" #include "include/cppgc/visitor.h" +#include "src/base/logging.h" #include "src/base/macros.h" #include "src/base/platform/time.h" #include "src/heap/base/incremental-marking-schedule.h" @@ -238,20 +239,15 @@ class V8_EXPORT_PRIVATE Marker final : public MarkerBase { template void MarkerBase::WriteBarrierForObject(HeapObjectHeader& header) { - // The barrier optimizes for the bailout cases: - // - kDijkstra: Marked objects. - // - kSteele: Unmarked objects. - switch (type) { - case MarkerBase::WriteBarrierType::kDijkstra: - if (!header.TryMarkAtomic()) { - return; - } - break; - case MarkerBase::WriteBarrierType::kSteele: - if (!header.IsMarked()) { - return; - } - break; + // The Dijkstra barrier bails out on marked objects. + // The Steele barrier marks the object regardless of its current state. + // It's important to make sure the object is marked here since unmarking + // below for in construction objects expects a marked object. + const bool was_already_marked = !header.TryMarkAtomic(); + if (type == MarkerBase::WriteBarrierType::kDijkstra) { + if (was_already_marked) { + return; + } } // The barrier fired. Filter out in-construction objects here. This possibly @@ -267,13 +263,18 @@ void MarkerBase::WriteBarrierForObject(HeapObjectHeader& header) { return; } - switch (type) { - case MarkerBase::WriteBarrierType::kDijkstra: - mutator_marking_state_.write_barrier_worklist().Push(&header); - break; - case MarkerBase::WriteBarrierType::kSteele: - mutator_marking_state_.retrace_marked_objects_worklist().Push(&header); - break; + DCHECK_IMPLIES(was_already_marked, + type == MarkerBase::WriteBarrierType::kSteele); + if ((type == MarkerBase::WriteBarrierType::kDijkstra) || + !was_already_marked) { + // Push previously unmarked objects to the write barrier worklist, even for + // the Steele barrier, since popping from that worklist would also update + // the marked bytes counter. + mutator_marking_state_.write_barrier_worklist().Push(&header); + } else { + DCHECK((type == MarkerBase::WriteBarrierType::kSteele) && + was_already_marked); + mutator_marking_state_.retrace_marked_objects_worklist().Push(&header); } } diff --git a/deps/v8/src/maglev/maglev-compiler.cc b/deps/v8/src/maglev/maglev-compiler.cc index 06dd7c5e3010..74236225fc0a 100644 --- a/deps/v8/src/maglev/maglev-compiler.cc +++ b/deps/v8/src/maglev/maglev-compiler.cc @@ -285,6 +285,8 @@ bool MaglevCompiler::Compile(LocalIsolate* local_isolate, // FunctionEntryStackCheck runs. A frame no larger than the smallest OS page // cannot step over a guard page, so bail out rather than emit an unchecked // stack pointer jump of arbitrary size. + // When replacing this bailout with a different strategy, consider doing the + // same in Turboshaft as well. if (graph->tagged_stack_slots() + graph->untagged_stack_slots() > kMaxStackSlots) { return false; diff --git a/deps/v8/src/maglev/maglev-graph-builder.cc b/deps/v8/src/maglev/maglev-graph-builder.cc index ca199bab0fab..d9ed8b2c9fbd 100644 --- a/deps/v8/src/maglev/maglev-graph-builder.cc +++ b/deps/v8/src/maglev/maglev-graph-builder.cc @@ -6368,6 +6368,7 @@ void MaglevGraphBuilder::AdvanceThroughContinuationForPolymorphicPropertyLoad( while (iterator_.current_offset() < continuation.last_continuation) { iterator_.Advance(); UpdateSourceAndBytecodePosition(iterator_.current_offset()); + PrewalkBytecode(); } } diff --git a/deps/v8/src/objects/js-function.cc b/deps/v8/src/objects/js-function.cc index 585d97d436c3..310ed57d8ade 100644 --- a/deps/v8/src/objects/js-function.cc +++ b/deps/v8/src/objects/js-function.cc @@ -991,8 +991,9 @@ void JSFunction::EnsureHasInitialMap(Isolate* isolate, // system. // TODO(leszeks): Figure out if this CHECK is needed. CHECK(IsJSReceiver(*prototype)); - JSFunction::SetInitialMap(isolate, function, map, prototype); + // Initialize slack tracking before publishing the map to concurrent readers. map->StartInobjectSlackTracking(); + JSFunction::SetInitialMap(isolate, function, map, prototype); } namespace { @@ -1183,11 +1184,12 @@ bool FastInitializeDerivedMap(Isolate* isolate, Map::CopyInitialMap(isolate, constructor_initial_map, instance_size, in_object_properties, unused_property_fields); map->set_new_target_is_base(false); + // Initialize slack tracking before publishing the map to concurrent readers. + map->set_construction_counter(Map::kNoSlackTracking); + map->StartInobjectSlackTracking(); DirectHandle prototype(new_target->instance_prototype(), isolate); JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor); DCHECK(IsJSReceiver(new_target->instance_prototype())); - map->set_construction_counter(Map::kNoSlackTracking); - map->StartInobjectSlackTracking(); return true; } diff --git a/deps/v8/src/objects/map-inl.h b/deps/v8/src/objects/map-inl.h index 1cbddb0d9879..63cfe9edd10f 100644 --- a/deps/v8/src/objects/map-inl.h +++ b/deps/v8/src/objects/map-inl.h @@ -1258,9 +1258,12 @@ void Map::InobjectSlackTrackingStep(Isolate* isolate) { DCHECK(IsUndefined(GetBackPointer())); if (!this->IsInobjectSlackTrackingInProgress()) return; int counter = construction_counter(); - set_construction_counter(counter - 1); if (counter == kSlackTrackingCounterEnd) { + // The last decrement must happen under the map_updater_access mutex, + // together with the instance size changes. MapUpdater::CompleteInobjectSlackTracking(isolate, this); + } else { + set_construction_counter(counter - 1); } } diff --git a/deps/v8/src/objects/map-updater.cc b/deps/v8/src/objects/map-updater.cc index f1437356d69e..84999a3a34da 100644 --- a/deps/v8/src/objects/map-updater.cc +++ b/deps/v8/src/objects/map-updater.cc @@ -400,7 +400,9 @@ std::optional> MapUpdater::TryUpdateNoLock(Isolate* isolate, ElementsKind to_kind = old_map->elements_kind(); IntegrityLevelTransitionInfo info(old_map); - if (root_map->is_extensible() != old_map->is_extensible()) { + if (!root_map->is_extensible() && old_map != root_map) { + return {}; + } else if (root_map->is_extensible() != old_map->is_extensible()) { DCHECK(!old_map->is_extensible()); DCHECK(root_map->is_extensible()); info = DetectIntegrityLevelTransitions(old_map, isolate, &no_gc, cmode); @@ -412,7 +414,7 @@ std::optional> MapUpdater::TryUpdateNoLock(Isolate* isolate, DCHECK(to_kind == DICTIONARY_ELEMENTS || to_kind == SLOW_STRING_WRAPPER_ELEMENTS || IsTypedArrayOrRabGsabTypedArrayElementsKind(to_kind) || - IsAnyHoleyNonextensibleElementsKind(to_kind)); + IsAnyNonextensibleElementsKind(to_kind)); to_kind = info.integrity_level_source_map->elements_kind(); } if (from_kind != to_kind) { @@ -622,6 +624,8 @@ MapUpdater::State MapUpdater::FindRootMap() { direct_handle(root_map_->prototype(), isolate_), root_map_->instance_type())) { return Normalize("Normalize_NotEquivalent"); + } else if (!root_map_->is_extensible() && *old_map_ != *root_map_) { + return Normalize("Normalize_PrivateSymbolsOnNonExtensible"); } else if (old_map_->is_extensible() != root_map_->is_extensible()) { DCHECK(!old_map_->is_extensible()); DCHECK(root_map_->is_extensible()); diff --git a/deps/v8/src/objects/map.cc b/deps/v8/src/objects/map.cc index a6f6a26a9451..cdc41455019e 100644 --- a/deps/v8/src/objects/map.cc +++ b/deps/v8/src/objects/map.cc @@ -603,10 +603,7 @@ MaybeHandle Map::CopyWithField(Isolate* isolate, DirectHandle map, MaybeObjectDirectHandle wrapped_type = WrapFieldType(type); Descriptor d = Descriptor::DataField(name, offset, attributes, constness, representation, wrapped_type); - Handle new_map = Map::CopyAddDescriptor(isolate, map, &d, flag); - - new_map->AccountAddedPropertyField(); - return new_map; + return Map::CopyAddDescriptor(isolate, map, &d, flag); } MaybeHandle Map::CopyWithConstant(Isolate* isolate, DirectHandle map, @@ -1615,6 +1612,10 @@ Handle Map::ShareDescriptor(Isolate* isolate, DirectHandle map, DisallowGarbageCollection no_gc; descriptors->Append(descriptor); result->InitializeDescriptors(*descriptors); + // Account for the field before publishing the transition. + if (descriptor->GetDetails().location() == PropertyLocation::kField) { + result->AccountAddedPropertyField(); + } } DCHECK(result->NumberOfOwnDescriptors() == map->NumberOfOwnDescriptors() + 1); @@ -2333,7 +2334,12 @@ Handle Map::CopyAddDescriptor(Isolate* isolate, DirectHandle map, DescriptorArray::CopyUpTo(isolate, descriptors, nof, 1); new_descriptors->Append(descriptor); - return CopyReplaceDescriptors(isolate, map, new_descriptors, flag, + auto InitMap = [&](DirectHandle new_map) { + if (descriptor->GetDetails().location() == PropertyLocation::kField) { + new_map->AccountAddedPropertyField(); + } + }; + return CopyReplaceDescriptors(isolate, map, new_descriptors, flag, InitMap, descriptor->GetKey(), "CopyAddDescriptor", SIMPLE_PROPERTY_TRANSITION); } diff --git a/deps/v8/src/runtime/runtime-literals.cc b/deps/v8/src/runtime/runtime-literals.cc index 312f5ddcf904..4a3cfca512de 100644 --- a/deps/v8/src/runtime/runtime-literals.cc +++ b/deps/v8/src/runtime/runtime-literals.cc @@ -856,6 +856,14 @@ static bool IsDefaultFunctionPrototype(DirectHandle js_proto, // Object function prototype's map. Tagged proto_map = js_proto->map(); + // Check that the prototype is a plain JSObject with no elements. + if (proto_map->instance_type() != JS_OBJECT_TYPE) { + return false; + } + if (js_proto->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) { + return false; + } + // Check that given function.prototype object has a default initial state: // it's extensible. if (!proto_map->is_extensible()) { @@ -868,13 +876,24 @@ static bool IsDefaultFunctionPrototype(DirectHandle js_proto, } // it has exactly one "constructor" property installed. - if (js_proto->property_dictionary()->NumberOfElements() != 1) { - return false; - } - if (js_proto->property_dictionary() - ->FindEntry(isolate, isolate->factory()->constructor_string()) - .is_not_found()) { - return false; + if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { + if (js_proto->property_dictionary_swiss()->NumberOfElements() != 1) { + return false; + } + if (js_proto->property_dictionary_swiss() + ->FindEntry(isolate, isolate->factory()->constructor_string()) + .is_not_found()) { + return false; + } + } else { + if (js_proto->property_dictionary()->NumberOfElements() != 1) { + return false; + } + if (js_proto->property_dictionary() + ->FindEntry(isolate, isolate->factory()->constructor_string()) + .is_not_found()) { + return false; + } } // its prototype is the original and unmodified Object.prototype object. @@ -907,7 +926,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { } DirectHandle acc_fun = Cast(obj); - if (!acc_fun->has_prototype_slot()) { + if (!acc_fun->has_prototype_slot() || !acc_fun->has_prototype_property()) { RETURN_RESULT_OR_FAILURE( isolate, SetPrototypePropertiesSlow(isolate, context, obj, object_boilerplate_description, @@ -937,18 +956,21 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { feedback_cell_array, current_slot)); } - if (IsSpecialReceiverMap(js_proto->map())) { - RETURN_RESULT_OR_FAILURE( - isolate, SetPrototypePropertiesSlow(isolate, context, obj, - object_boilerplate_description, - feedback_cell_array, current_slot)); - } - - if (!JSObject::IsExtensible(isolate, js_proto)) { - RETURN_RESULT_OR_FAILURE( - isolate, SetPrototypePropertiesSlow(isolate, context, obj, - object_boilerplate_description, - feedback_cell_array, current_slot)); + // Use the slow path for exotic receivers, interceptors, or access checks. + { + Tagged js_proto_map = js_proto->map(); + if (IsCustomElementsReceiverMap(js_proto_map) || + IsJSTypedArrayMap(js_proto_map) || + IsAlwaysSharedSpaceJSObjectMap(js_proto_map) || + js_proto_map->is_access_check_needed() || + js_proto_map->has_named_interceptor() || + js_proto_map->has_indexed_interceptor() || + !JSObject::IsExtensible(isolate, js_proto)) { + RETURN_RESULT_OR_FAILURE( + isolate, SetPrototypePropertiesSlow( + isolate, context, obj, object_boilerplate_description, + feedback_cell_array, current_slot)); + } } bool is_default_func_prototype = @@ -977,13 +999,14 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { DirectHandle accessor_info = isolate->factory()->lazy_closure_accessor(); - JSObject::SetAccessor(js_proto, name, accessor_info, - PropertyAttributes::NONE) - .Check(); + RETURN_FAILURE_ON_EXCEPTION( + isolate, JSObject::SetAccessor(js_proto, name, accessor_info, + PropertyAttributes::NONE)); } else { - Object::TransitionAndWriteDataProperty( - &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) - .Check(); + MAYBE_RETURN( + Object::TransitionAndWriteDataProperty( + &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed), + ReadOnlyRoots(isolate).exception()); } result = value; } @@ -1029,7 +1052,8 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { current_slot++; } it.UpdateProtector(); - Object::SetDataProperty(&it, value).Check(); + MAYBE_RETURN(Object::SetDataProperty(&it, value), + ReadOnlyRoots(isolate).exception()); } else { value = InstantiateIfSharedFunctionInfo( context, isolate, js_proto, value, feedback_cell_array, @@ -1042,13 +1066,15 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { // doesn't invalidate protectors, so do it here for e.g. `then` or // `next` added to a watched prototype. it.UpdateProtector(); - JSObject::SetAccessor(js_proto, Cast(key), accessor_info, - PropertyAttributes::NONE) - .Check(); + RETURN_FAILURE_ON_EXCEPTION( + isolate, + JSObject::SetAccessor(js_proto, Cast(key), accessor_info, + PropertyAttributes::NONE)); } else { - Object::TransitionAndWriteDataProperty( - &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) - .Check(); + MAYBE_RETURN( + Object::TransitionAndWriteDataProperty( + &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed), + ReadOnlyRoots(isolate).exception()); } } result = value; diff --git a/deps/v8/test/mjsunit/compiler/regress-574339635.js b/deps/v8/test/mjsunit/compiler/regress-574339635.js new file mode 100644 index 000000000000..f131352bccf4 --- /dev/null +++ b/deps/v8/test/mjsunit/compiler/regress-574339635.js @@ -0,0 +1,37 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --turbofan --allow-natives-syntax + +const P = Array.prototype; +const Obj = Object; +let s; + +function foo(o, t) { + o[0]; + let r = 0; + if (t) r = Obj`hello`; + const v = o.x; + s = r; + return v; +} + +new WeakMap().set(P, 1); +for (const _ in []) {} +P[0] = 1; +P.x = 1; +Object.defineProperty(P, "x", {value: 1.5, enumerable: false}); + +%PrepareFunctionForOptimization(foo); +foo(P, false); foo(P, false); foo(P, false); +%OptimizeFunctionOnNextCall(foo); +assertEquals(1.5, foo(P, false)); + +for (let i = 0; i < 400; i += 3) { + P["p" + i] = "x"; + P["p" + (i + 1)] = "corrupted"; + P["p" + (i + 2)] = 0; +} + +assertEquals(1.5, foo(P, true)); diff --git a/deps/v8/test/mjsunit/compiler/regress-578688670.js b/deps/v8/test/mjsunit/compiler/regress-578688670.js new file mode 100644 index 000000000000..b16f0752f28e --- /dev/null +++ b/deps/v8/test/mjsunit/compiler/regress-578688670.js @@ -0,0 +1,56 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax + +let rejected_error; +function CustomPromise(executor) { + executor(() => {}, (err) => { rejected_error = err; }); +} + +let captured_reject_closure; +CustomPromise.resolve = function(thenable) { + return thenable; +}; + +const pending_thenables = [ + { then(resolve, reject) { captured_reject_closure = reject; } }, + { then() {} }, + { then() {} }, +]; + +Promise.any.call(CustomPromise, pending_thenables); + +// Store the reject element closure on a constant holder so that inlining +// invoke_reject_closure into call_reject_closure constant-folds the closure +// target and its initial FunctionContext. +const closure_holder = { reject: captured_reject_closure }; + +function invoke_reject_closure(closure, should_call) { + if (should_call) closure('err'); +} + +function call_reject_closure(should_call) { + invoke_reject_closure(closure_holder.reject, should_call); +} + +// Make the call site inside invoke_reject_closure polymorphic before +// calling captured_reject_closure so Maglev/TurboFan compile the call without +// invoking captured_reject_closure during warmup. +%PrepareFunctionForOptimization(invoke_reject_closure); +invoke_reject_closure(() => {}, true); +invoke_reject_closure((x) => x, true); + +%PrepareFunctionForOptimization(call_reject_closure); +call_reject_closure(false); +%OptimizeFunctionOnNextCall(call_reject_closure); +call_reject_closure(false); + +// Repeated calls to the same reject element closure must be ignored after the +// first call and must not decrement remainingElementsCount to 0. +call_reject_closure(true); +call_reject_closure(true); +call_reject_closure(true); + +assertEquals(undefined, rejected_error); diff --git a/deps/v8/test/mjsunit/maglev/regress-574021176.js b/deps/v8/test/mjsunit/maglev/regress-574021176.js new file mode 100644 index 000000000000..780b5b1c8cfa --- /dev/null +++ b/deps/v8/test/mjsunit/maglev/regress-574021176.js @@ -0,0 +1,44 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax + +let g = () => class {}; + +function* gen(o) { + // Context-allocated, maybe-assigned variable so the function context uses a + // ContextCell for `x`. + let x = 42; + () => x = 0; + + // Polymorphic property load with a call continuation. The class literal + // pushes a `CLASS_SCOPE` context (needed for `super.x`) before evaluating + // `extends g()` and pops it after defining the class. When `g()` aborts in + // every polymorphic arm, skipping the rest of the continuation must still pop + // that `CLASS_SCOPE`. + o.f(class extends g() { + constructor() { + super.x; + } + }); + + // In dead code after all polymorphic arms abort, `SuspendGenerator` records + // `current_scope_info_` on the `ResumeGenerator` merge state. + yield; + + // Loads `x` using the `ScopeInfo` from the resume merge state (which must be + // the generator's `FUNCTION_SCOPE` with cells, not the leaked `CLASS_SCOPE`). + return x; +} + +const suspended = gen({f() {}}); +suspended.next(); + +g = class {}; +%PrepareFunctionForOptimization(gen); +assertThrows(() => gen({f() {}}).next(), TypeError); +assertThrows(() => gen({a: 1, f() {}}).next(), TypeError); + +%OptimizeFunctionOnNextCall(gen); +assertEquals(42, suspended.next().value); diff --git a/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js b/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js new file mode 100644 index 000000000000..e01a1280d53f --- /dev/null +++ b/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js @@ -0,0 +1,65 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --mock-arraybuffer-allocator + +// Test exception propagation when a TypedArray prototype rejects a BigInt store. +(function testLargeTypedArrayStoreException() { + let ta; + try { + ta = new Int8Array(4294967296); + } catch (e) { + assertInstanceof(e, RangeError); + return; + } + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1n; + F.prototype.named = 1; + }, TypeError); + + assertEquals(undefined, ta.named); +})(); + +// Test exception propagation when valueOf throws during a TypedArray store. +(function testLargeTypedArrayValueOfException() { + let ta; + try { + ta = new Uint8Array(4294967296); + } catch (e) { + assertInstanceof(e, RangeError); + return; + } + + const badValue = { + valueOf() { + throw new URIError(); + }, + }; + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = badValue; + F.prototype.named = 2; + }, URIError); + + assertEquals(undefined, ta.named); +})(); + +// Test exception propagation on a small BigInt64Array prototype. +(function testBigIntTypedArrayStoreException() { + const ta = new BigInt64Array(4); + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1; + F.prototype.named = 3; + }, TypeError); + + assertEquals(undefined, ta.named); +})(); diff --git a/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js b/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js new file mode 100644 index 000000000000..30e50a33d98e --- /dev/null +++ b/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js @@ -0,0 +1,63 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Create a dictionary-mode TypedArray that mimics a default function prototype. +function makeDictionaryTypedArray() { + const ta = new Uint8Array(4); + ta.temp = 1; + delete ta.temp; + ta.constructor = Object; + Object.setPrototypeOf(ta, Object.prototype); + return ta; +} + +// Test integer indices above kMaxElementIndex on a TypedArray prototype. +(function testTypedArrayLargeIntegerIndices() { + const ta = makeDictionaryTypedArray(); + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1; + F.prototype['4294967296'] = 2; + F.prototype.named = 3; + + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '4294967295')); + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '4294967296')); + assertEquals(undefined, ta['4294967295']); + assertEquals(undefined, ta['4294967296']); + assertEquals(3, ta.named); +})(); + +// Test canonical numeric index strings on a TypedArray prototype. +(function testTypedArraySpecialNumericIndices() { + const ta = makeDictionaryTypedArray(); + function F() {} + F.prototype = ta; + F.prototype['1.5'] = 1; + F.prototype['-0'] = 2; + F.prototype.named = 3; + + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '1.5')); + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '-0')); + assertEquals(undefined, ta['1.5']); + assertEquals(undefined, ta['-0']); + assertEquals(3, ta.named); +})(); + +// Test a dictionary-mode String wrapper as a function prototype. +(function testStringWrapperPrototype() { + const wrapper = new String('abc'); + wrapper.temp = 1; + delete wrapper.temp; + wrapper.constructor = Object; + Object.setPrototypeOf(wrapper, Object.prototype); + + function F() {} + F.prototype = wrapper; + F.prototype.first = 10; + F.prototype.second = 20; + + assertEquals(10, wrapper.first); + assertEquals(20, wrapper.second); + assertEquals('a', wrapper[0]); +})(); diff --git a/deps/v8/test/mjsunit/regress/regress-577318603.js b/deps/v8/test/mjsunit/regress/regress-577318603.js new file mode 100644 index 000000000000..e89da0196585 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-577318603.js @@ -0,0 +1,74 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --no-js-nonextensible-applies-to-private + +class Base { + constructor(o) { + return o; + } +} + +class InstallPrivateField extends Base { + #p = 1; + static setP(o, v) { + o.#p = v; + } + static getP(o) { + return o.#p; + } +} + +// Private field added onto a non-extensible root map before Object.seal. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + new InstallPrivateField(obj); + Object.seal(obj); + InstallPrivateField.setP(obj, 1.5); + + assertEquals(1.5, InstallPrivateField.getP(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} + +// Object.seal on a non-extensible root map before adding a private field. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + Object.seal(obj); + new InstallPrivateField(obj); + InstallPrivateField.setP(obj, 1.5); + + assertEquals(1.5, InstallPrivateField.getP(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} + +// Constness generalization of a root-map property after Object.seal on a +// non-extensible dictionary-elements root map must not drop the seal +// transition. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + Object.seal(obj); + obj.x = 2; + + assertEquals(2, obj.x); + assertTrue(Object.isSealed(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} diff --git a/deps/v8/test/mjsunit/turboshaft/regress-574660146.js b/deps/v8/test/mjsunit/turboshaft/regress-574660146.js new file mode 100644 index 000000000000..bc82a2684baa --- /dev/null +++ b/deps/v8/test/mjsunit/turboshaft/regress-574660146.js @@ -0,0 +1,41 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --turbofan --no-maglev + +function cb() { + return arguments.length; +} +%NeverOptimizeFunction(cb); + +// Passing `kArgCount` distinct expressions to a non-inlined call keeps all of +// them live simultaneously at the call site, requiring > 1024 stack slots +// (> 4KB on both 32-bit and 64-bit targets) while keeping the bytecode small +// enough to be compiled. +const kArgCount = 1100; +const args = Array.from({length: kArgCount}, (_, k) => `p ^ ${k + 1}`); + +function buildWideFrame(osr) { + return Function('x', 'cb', 'p', ` + let result = 0; + for (let i = 0; i < 10; ++i) { + ${osr ? 'if (i === 5) %OptimizeOsr();' : ''} + if (x < 0) return cb(${args}); + result = (x + i) | 0; + } + return result; + `); +} + +const wide = buildWideFrame(false); +%PrepareFunctionForOptimization(wide); +assertEquals(kArgCount, wide(-1, cb, 1)); +assertEquals(10, wide(1, cb, 1)); +%OptimizeFunctionOnNextCall(wide); +assertEquals(10, wide(1, cb, 1)); + +const wideOsr = buildWideFrame(true); +%PrepareFunctionForOptimization(wideOsr); +assertEquals(kArgCount, wideOsr(-1, cb, 1)); +assertEquals(10, wideOsr(1, cb, 1)); diff --git a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc index 9df26a108ca6..bfe99ffa50e1 100644 --- a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc @@ -476,11 +476,11 @@ TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersWhenMarkingIsOn) { } } -TEST_F(WriteBarrierTest, SteeleWriteBarrierBailoutIfNotMarked) { +TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersEvenIfNotMarked) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle(), object1); { - ExpectNoWriteBarrierFires scope(marker(), {object1}); + ExpectWriteBarrierFires scope(marker(), {object1}); WriteBarrierParams params; EXPECT_EQ(WriteBarrierType::kMarking, HeapConsistency::GetWriteBarrierType( From c32c7e8d87b39a68ea0feb569b59233edb1f1aed Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 2 Nov 2026 19:53:13 -0500 Subject: [PATCH 083/195] deps: update simdjson to 5.1.0 PR-URL: https://github.com/nodejs/node/pull/67172 Reviewed-By: Colin Ihrig Reviewed-By: Richard Lau Reviewed-By: Luigi Pinca --- deps/simdjson/simdjson.cpp | 4490 ++++----- deps/simdjson/simdjson.h | 18225 +++++++++++++---------------------- 2 files changed, 8551 insertions(+), 14164 deletions(-) diff --git a/deps/simdjson/simdjson.cpp b/deps/simdjson/simdjson.cpp index e8a2a24a04b4..08b0de352947 100644 --- a/deps/simdjson/simdjson.cpp +++ b/deps/simdjson/simdjson.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2026-10-14 15:49:28 -0400. version 5.0.5 Do not edit! */ +/* auto-generated on 2026-10-29 18:49:29 -0400. version 5.1.0 Do not edit! */ /* including simdjson.cpp: */ /* begin file simdjson.cpp */ #define SIMDJSON_SRC_SIMDJSON_CPP @@ -12378,6 +12378,23 @@ class dom_parser_implementation { */ simdjson_warn_unused virtual error_code stage2_next(dom::document &doc) noexcept = 0; + /** @private A stage 2 entry point for parser::parse_many: see stage2_next_function. */ + typedef error_code (*stage2_next_function_t)(dom_parser_implementation &impl, dom::document &doc); + + /** + * @private For internal implementation use + * + * Get a function that does what stage2_next() does. dom::document_stream picks + * it once per batch and calls it for each document of the batch, so that the + * choice between the bounded and the unchecked walk costs nothing per document. + * + * Overridden by each implementation. + * + * @param bounded Whether the documents must be bounded to the structural + * indexes of the batch (see _bounded_batch). + */ + virtual stage2_next_function_t stage2_next_function(bool bounded) const noexcept = 0; + /** * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There * must be an unescaped quote terminating the string. It returns the final output @@ -12502,6 +12519,14 @@ class dom_parser_implementation { */ bool _unpadded{false}; + /** + * Whether stage 2 must bound the documents of the batch indexed by the last + * stage 1 run to its structural indexes (dom::document_stream sets it, see + * document_stream::run_stage1, and passes it to stage2_next_function). Other + * batches keep the unchecked walk. + */ + bool _bounded_batch{false}; + protected: // Declaring these so that subclasses can use them to implement their constructors. @@ -14960,31 +14985,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin }; } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - (is_backslash | is_quote | is_control).to_bitmask64() - }; -} - - } // unnamed namespace } // namespace arm64 @@ -15378,6 +15378,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -16139,21 +16140,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -16699,57 +16685,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -16842,50 +16777,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -16991,8 +16882,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -17082,8 +16976,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -17163,88 +17060,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -17313,8 +17128,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -17406,8 +17224,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -18463,31 +18284,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin }; } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - (is_backslash | is_quote | is_control).to_bitmask64() - }; -} - - } // unnamed namespace } // namespace arm64 @@ -18656,6 +18452,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -18664,47 +18471,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -18729,6 +18495,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -19429,6 +19227,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -20110,7 +19922,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -20144,6 +19957,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -20200,8 +20027,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -20245,7 +20077,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -20264,12 +20096,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -20687,7 +20524,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -20704,8 +20541,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -20713,8 +20550,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -20762,7 +20599,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -20770,6 +20607,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -20777,7 +20617,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -20789,8 +20629,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -20807,29 +20647,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -20857,16 +20697,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -20896,8 +20736,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -20907,8 +20757,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -21467,6 +21323,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -21896,18 +21776,6 @@ simdjson_inline bool is_ascii(const simd8x64& input) { return bits.max_val() < 0x80u; } -simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { - simd8 is_second_byte = prev1 >= uint8_t(0xc0u); - simd8 is_third_byte = prev2 >= uint8_t(0xe0u); - simd8 is_fourth_byte = prev3 >= uint8_t(0xf0u); - // Use ^ instead of | for is_*_byte, because ^ is commutative, and the caller is using ^ as well. - // This will work fine because we only have to report errors for cases with 0-1 lead bytes. - // Multiple lead bytes implies 2 overlapping multibyte characters, and if that happens, there is - // guaranteed to be at least *one* lead byte that is part of only 1 other multibyte character. - // The error will be detected there. - return is_second_byte ^ is_third_byte ^ is_fourth_byte; -} - simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 @@ -21947,7 +21815,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -22681,30 +22553,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 32; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace haswell } // namespace simdjson @@ -23095,6 +22943,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -23856,21 +23705,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -24416,57 +24250,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -24559,50 +24342,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -24708,8 +24447,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -24799,8 +24541,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -24880,88 +24625,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -25030,8 +24693,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -25123,8 +24789,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -26031,30 +25700,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 32; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace haswell } // namespace simdjson @@ -26220,6 +25865,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -26228,47 +25884,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -26293,6 +25908,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -26993,6 +26640,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -27674,7 +27335,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -27708,6 +27370,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -27764,8 +27440,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -27809,7 +27490,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -27828,12 +27509,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -28251,7 +27937,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -28268,8 +27954,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -28277,8 +27963,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -28326,7 +28012,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -28334,6 +28020,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -28341,7 +28030,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -28353,8 +28042,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -28371,29 +28060,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -28421,16 +28110,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -28460,8 +28149,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -28471,8 +28170,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -29031,6 +28736,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -29441,14 +29170,6 @@ simdjson_inline bool is_ascii(const simd8x64& input) { return input.reduce_or().is_ascii(); } -simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { - simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 - simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 - simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 - // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. - return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); -} - simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 @@ -29488,7 +29209,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -30157,34 +29882,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } - -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 64; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(uint64_t(escape_bits)); } - - __mmask64 escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - __mmask64 is_quote = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('"')); - __mmask64 is_backslash = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('\\')); - __mmask64 is_control = _mm512_cmplt_epi8_mask(v, _mm512_set1_epi8(32)); - return { - (is_backslash | is_quote | is_control) - }; -} - - - - } // unnamed namespace } // namespace icelake } // namespace simdjson @@ -30635,6 +30332,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -31396,21 +31094,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -31956,57 +31639,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -32099,50 +31731,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -32248,8 +31836,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -32339,8 +31930,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -32420,88 +32014,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -32570,8 +32082,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -32663,8 +32178,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -33506,34 +33024,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } - -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 64; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(uint64_t(escape_bits)); } - - __mmask64 escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - __mmask64 is_quote = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('"')); - __mmask64 is_backslash = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('\\')); - __mmask64 is_control = _mm512_cmplt_epi8_mask(v, _mm512_set1_epi8(32)); - return { - (is_backslash | is_quote | is_control) - }; -} - - - - } // unnamed namespace } // namespace icelake } // namespace simdjson @@ -33759,6 +33249,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -33767,47 +33268,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -33832,6 +33292,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -34532,6 +34024,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -35213,7 +34719,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -35247,6 +34754,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -35303,8 +34824,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -35348,7 +34874,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -35367,12 +34893,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -35790,7 +35321,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -35807,8 +35338,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -35816,8 +35347,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -35865,7 +35396,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -35873,6 +35404,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -35880,7 +35414,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -35892,8 +35426,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -35910,29 +35444,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -35960,16 +35494,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -35999,8 +35533,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -36010,8 +35554,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -36570,6 +36120,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -36979,14 +36553,6 @@ simdjson_inline bool is_ascii(const simd8x64& input) { return input.reduce_or().is_ascii(); } -simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { - simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 - simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 - simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 - // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. - return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); -} - simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 @@ -37070,7 +36636,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -37915,31 +37485,6 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - // We store it as a 64-bit bitmask even though we only need 16 bits. - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace ppc64 } // namespace simdjson @@ -38332,6 +37877,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -39093,21 +38639,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -39653,57 +39184,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -39796,50 +39276,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -39945,8 +39381,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -40036,8 +39475,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -40117,88 +39559,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -40267,8 +39627,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -40360,8 +39723,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -41377,31 +40743,6 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - // We store it as a 64-bit bitmask even though we only need 16 bits. - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace ppc64 } // namespace simdjson @@ -41569,6 +40910,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -41577,47 +40929,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -41642,6 +40953,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -42342,6 +41685,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -43023,7 +42380,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -43057,6 +42415,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -43113,8 +42485,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -43158,7 +42535,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -43177,12 +42554,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -43600,7 +42982,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -43617,8 +42999,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -43626,8 +43008,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -43675,7 +43057,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -43683,6 +43065,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -43690,7 +43075,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -43702,8 +43087,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -43720,29 +43105,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -43770,16 +43155,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -43809,8 +43194,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -43820,8 +43215,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -44380,6 +43781,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -44763,14 +44188,6 @@ simdjson_inline bool is_ascii(const simd8x64& input) { return input.reduce_or().saturating_sub(0x7fu).bits_not_set_anywhere(); } -simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { - simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 - simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 - simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 - // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. - return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); -} - simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 @@ -44810,7 +44227,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -45965,30 +45386,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace westmere } // namespace simdjson @@ -46379,6 +45776,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -47140,21 +46538,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -47700,57 +47083,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -47843,50 +47175,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -47992,8 +47280,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -48083,8 +47374,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -48164,88 +47458,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -48314,8 +47526,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -48407,8 +47622,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -49732,30 +48950,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace westmere } // namespace simdjson @@ -49921,6 +49115,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -49929,47 +49134,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -49994,6 +49158,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -50694,6 +49890,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -51375,7 +50585,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -51409,6 +50620,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -51465,8 +50690,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -51510,7 +50740,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -51529,12 +50759,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -51952,7 +51187,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -51969,8 +51204,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -51978,8 +51213,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -52027,7 +51262,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -52035,6 +51270,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -52042,7 +51280,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -52054,8 +51292,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -52072,29 +51310,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -52122,16 +51360,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -52161,8 +51399,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -52172,8 +51420,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -52732,6 +51986,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -53146,14 +52424,6 @@ simdjson_inline bool is_ascii(const simd8x64& input) { return input.reduce_or().is_ascii(); } -simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { - simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 - simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 - simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 - // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. - return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); -} - simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { simd8 is_third_byte = prev2.saturating_sub(0xe0u-0x80); // Only 111_____ will be >= 0x80 simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-0x80); // Only 1111____ will be >= 0x80 @@ -53194,7 +52464,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -53860,30 +53134,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lasx } // namespace simdjson @@ -54278,6 +53528,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -55039,21 +54290,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -55599,57 +54835,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -55742,50 +54927,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -55891,8 +55032,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -55982,8 +55126,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -56063,26 +55210,37 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } +simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign // - bool negative = (*src == '-'); - src += uint8_t(negative); + bool negative = (*(src + 1) == '-'); + src += uint8_t(negative) + 1; // // Parse the integer part. // uint64_t i = 0; const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } + bool leading_zero; + parse_float_integer_digits(p, i, leading_zero); // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } + if ( p == src ) { +#if SIMDJSON_ENABLE_NAN_INF + // If there are no leading digits, attempt to parse numbers that are either + // NaN or Infinity + if (atomparsing::is_valid_inf_in_string(src)) { + double inf = std::numeric_limits::infinity(); + return negative ? -inf : inf; + } + + if (atomparsing::is_valid_nan_in_string(src)) { + return std::numeric_limits::quiet_NaN(); + } +#endif + + return INCORRECT_TYPE; + } if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } // @@ -56090,12 +55248,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 // int64_t exponent = 0; bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { + if (simdjson_likely(*p == '.')) { p++; const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } + parse_fraction_digits(p, i); + if (p == start_decimal_digits) { return NUMBER_ERROR; } // no decimal digits exponent = -(p - start_decimal_digits); // Overflow check. More than 19 digits (minus the decimal) may be overflow. @@ -56104,7 +55261,7 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 // Skip leading 0.00000 and see if it still overflows const uint8_t *start_digits = src + 2; while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; + overflow = p-start_digits > 19; } } else { overflow = p-src > 19; @@ -56113,22 +55270,24 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 // // Parse the exponent // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { + if (*p == 'e' || *p == 'E') { p++; - if(p == src_end) { return NUMBER_ERROR; } bool exp_neg = *p == '-'; p += exp_neg || *p == '+'; uint64_t exp = 0; const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + while (parse_digit(*p, exp)) { p++; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } + if (*p != '"') { return NUMBER_ERROR; } overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; @@ -56139,13 +55298,15 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 if (simdjson_likely(!overflow)) { if (compute_float_64(exponent, i, negative, d)) { return d; } } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { + if (!parse_float_fallback(src - uint8_t(negative), &d)) { return NUMBER_ERROR; } return d; } -simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { +// Parse a JSON number held inside a JSON string into a binary32 (float) value. +// See parse_float for why we do not simply round parse_double_in_string. +simdjson_unused simdjson_inline simdjson_result parse_float_in_string(const uint8_t * src) noexcept { // // Check for minus sign // @@ -56165,12 +55326,12 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c // If there are no leading digits, attempt to parse numbers that are either // NaN or Infinity if (atomparsing::is_valid_inf_in_string(src)) { - double inf = std::numeric_limits::infinity(); + float inf = std::numeric_limits::infinity(); return negative ? -inf : inf; } if (atomparsing::is_valid_nan_in_string(src)) { - return std::numeric_limits::quiet_NaN(); + return std::numeric_limits::quiet_NaN(); } #endif @@ -56213,101 +55374,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if (*p != '"') { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), &d)) { - return NUMBER_ERROR; - } - return d; -} - -// Parse a JSON number held inside a JSON string into a binary32 (float) value. -// See parse_float for why we do not simply round parse_double_in_string. -simdjson_unused simdjson_inline simdjson_result parse_float_in_string(const uint8_t * src) noexcept { - // - // Check for minus sign - // - bool negative = (*(src + 1) == '-'); - src += uint8_t(negative) + 1; - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - bool leading_zero; - parse_float_integer_digits(p, i, leading_zero); - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { -#if SIMDJSON_ENABLE_NAN_INF - // If there are no leading digits, attempt to parse numbers that are either - // NaN or Infinity - if (atomparsing::is_valid_inf_in_string(src)) { - float inf = std::numeric_limits::infinity(); - return negative ? -inf : inf; - } - - if (atomparsing::is_valid_nan_in_string(src)) { - return std::numeric_limits::quiet_NaN(); - } -#endif - - return INCORRECT_TYPE; - } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely(*p == '.')) { - p++; - const uint8_t *start_decimal_digits = p; - parse_fraction_digits(p, i); - if (p == start_decimal_digits) { return NUMBER_ERROR; } // no decimal digits - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = p-start_digits > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if (*p == 'e' || *p == 'E') { - p++; - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -57143,30 +56214,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lasx } // namespace simdjson @@ -57336,6 +56383,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -57344,47 +56402,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -57409,6 +56426,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -58109,6 +57158,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -58790,7 +57853,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -58824,6 +57888,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -58880,8 +57958,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -58925,7 +58008,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -58944,12 +58027,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -59367,7 +58455,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -59384,8 +58472,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -59393,8 +58481,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -59442,7 +58530,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -59450,6 +58538,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -59457,7 +58548,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -59469,8 +58560,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -59487,29 +58578,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -59537,16 +58628,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -59576,8 +58667,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -59587,8 +58688,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -60147,6 +59254,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -60567,7 +59698,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -61214,30 +60349,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lsx } // namespace simdjson @@ -61630,6 +60741,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -62391,21 +61503,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -62951,57 +62048,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -63094,50 +62140,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -63243,8 +62245,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -63334,8 +62339,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -63415,88 +62423,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -63565,8 +62491,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -63658,8 +62587,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -64464,30 +63396,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lsx } // namespace simdjson @@ -64655,6 +63563,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -64663,47 +63582,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -64728,6 +63606,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -65428,6 +64338,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -66109,7 +65033,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -66143,6 +65068,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -66199,8 +65138,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -66244,7 +65188,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -66263,12 +65207,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -66686,7 +65635,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -66703,8 +65652,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -66712,8 +65661,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -66761,7 +65710,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -66769,6 +65718,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -66776,7 +65728,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -66788,8 +65740,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -66806,29 +65758,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -66856,16 +65808,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -66895,8 +65847,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -66906,8 +65868,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -67466,6 +66434,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -67890,7 +66882,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -68500,23 +67496,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin return { (v == '\\').to_bitmask(), (v == '"').to_bitmask() }; } -struct escaping { - static constexpr uint64_t BYTES_PROCESSED = sizeof(simd8); - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - return { ((v == '"') | (v == '\\') | (v == 32)).to_bitmask() }; -} - } // unnamed namespace } // namespace rvv_vls @@ -68969,6 +67948,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -69730,21 +68710,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -70290,57 +69255,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -70433,50 +69347,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -70582,8 +69452,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -70673,8 +69546,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -70754,88 +69630,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -70904,8 +69698,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -70997,8 +69794,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -71774,23 +70574,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin return { (v == '\\').to_bitmask(), (v == '"').to_bitmask() }; } -struct escaping { - static constexpr uint64_t BYTES_PROCESSED = sizeof(simd8); - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - return { ((v == '"') | (v == '\\') | (v == 32)).to_bitmask() }; -} - } // unnamed namespace } // namespace rvv_vls @@ -72391,6 +71174,17 @@ struct buf_block_reader { * @return the number of effective characters in the last block. */ simdjson_inline size_t get_remainder(uint8_t *dst) const; + /** + * Like get_remainder, for input followed by SIMDJSON_PADDING readable bytes. + * + * get_remainder writes the block with a memset and a memcpy of the remaining + * length, and the vector loads that read it back cannot be forwarded from + * those narrower stores: they wait until the stores reach the cache, which + * costs tens of cycles on every document. Here the block is put together in + * vector registers, from loads that may read into the padding, and written + * with full-width stores that the loads can forward from. + */ + simdjson_inline size_t get_remainder_padded(uint8_t *dst) const; simdjson_inline void advance(); private: const uint8_t *buf; @@ -72399,47 +71193,6 @@ struct buf_block_reader { size_t idx; }; -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text_64(const uint8_t *text) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i); i++) { - buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -// Routines to print masks and text for debugging bitmask operations -simdjson_unused static char * format_input_text(const simd8x64& in) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] < ' ') { buf[i] = '_'; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_input_text(const simd8x64& in, uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - in.store(reinterpret_cast(buf)); - for (size_t i=0; i); i++) { - if (buf[i] <= ' ') { buf[i] = '_'; } - if (!(mask & (size_t(1) << i))) { buf[i] = ' '; } - } - buf[sizeof(simd8x64)] = '\0'; - return buf; -} - -simdjson_unused static char * format_mask(uint64_t mask) { - static char buf[sizeof(simd8x64) + 1]; - for (size_t i=0; i<64; i++) { - buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; - } - buf[64] = '\0'; - return buf; -} - template simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} @@ -72464,6 +71217,38 @@ simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) return len - idx; } +// keep_mask[128 - n + i] is 0xFF for i < n and 0 otherwise. +alignas(64) static const uint8_t keep_mask[256] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +template +simdjson_inline size_t buf_block_reader::get_remainder_padded(uint8_t *dst) const { + static_assert(STEP_SIZE <= 128, "keep_mask covers blocks of up to 128 bytes"); + static_assert(sizeof(simd::simd8) <= SIMDJSON_PADDING, "a vector load at the last byte must stay in the padding"); + const size_t remaining = len - idx; + if (remaining == 0) { return 0; } + const simd::simd8 spaces = simd::simd8::splat(0x20); + for (size_t off = 0; off < STEP_SIZE; off += sizeof(simd::simd8)) { + simd::simd8 v = spaces; + if (off < remaining) { + // Starts before len, so it ends within the padding. + const simd::simd8 in = simd::simd8::load(buf + idx + off); + const simd::simd8 keep = simd::simd8::load(keep_mask + 128 - remaining + off); + v = spaces ^ ((in ^ spaces) & keep); + } + v.store(dst + off); + } + return remaining; +} + template simdjson_inline void buf_block_reader::advance() { idx += STEP_SIZE; @@ -73164,6 +71949,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -73845,7 +72644,8 @@ error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_pa // Take care of the last block (will always be there unless file is empty which is // not supposed to happen.) uint8_t block[STEP_SIZE]; - if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } + const size_t remaining = parser._unpadded ? reader.get_remainder(block) : reader.get_remainder_padded(block); + if (simdjson_unlikely(remaining == 0)) { return UNEXPECTED_ERROR; } indexer.step(block, reader); return indexer.finish(parser, reader.block_index(), len, partial); } @@ -73879,6 +72679,20 @@ simdjson_inline void json_structural_indexer::next(const simd::simd8x64 unescaped_chars_error |= block.non_quote_inside_string(unescaped); } +// Drops the unclosed string that ends a json_sequence batch and leaves its +// start in structural_indexes[n_structural_indexes]. The last index is its +// opening quote, unless the quote directly follows an RS: the scanner reads +// RS as a scalar byte, so it indexes the RS and not the quote. Keep the RS. +simdjson_inline void drop_json_sequence_unclosed_string(dom_parser_implementation &parser, size_t len) noexcept { + uint32_t quote = parser.structural_indexes[parser.n_structural_indexes - 1]; + if (parser.buf[quote] == '"') { + parser.n_structural_indexes--; + return; + } + while (quote < len && parser.buf[quote] != '"') { quote++; } + parser.structural_indexes[parser.n_structural_indexes] = quote; +} + simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { // Write out the final iteration's structurals indexer.write(uint32_t(idx-64), prev_structurals); @@ -73935,8 +72749,13 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !have_unclosed_string && ends_with_partial_scalar(parser, len)); + const bool partial_scalar = !have_unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -73980,7 +72799,7 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa // A discarded unclosed string also caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } scan_len = parser.structural_indexes[parser.n_structural_indexes]; } @@ -73999,12 +72818,17 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // The next batch starts at an RS, which the On-Demand API never reads as a + // string, a container or a separator. But an invalid last document may make + // it skip that RS as a value: repeat it, so that the index past it is not + // the next record, which can be a string that does not close in this batch. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. size_t scan_len = len; if(have_unclosed_string) { - parser.n_structural_indexes--; + drop_json_sequence_unclosed_string(parser, len); scan_len = parser.structural_indexes[parser.n_structural_indexes]; } uint32_t next_batch_start = uint32_t(len); @@ -74422,7 +73246,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -74439,8 +73263,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -74448,8 +73272,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -74497,7 +73321,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -74505,6 +73329,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -74512,7 +73339,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -74524,8 +73351,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -74542,29 +73369,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -74592,16 +73419,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -74631,8 +73458,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -74642,8 +73479,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -75202,6 +74045,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -75620,7 +74487,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY @@ -75793,23 +74664,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 1; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits; } - simdjson_inline int escape_index() { return 0; } - - bool escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - dst[0] = src[0]; - return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) }; -} - } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -76289,6 +75143,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -77050,21 +75905,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -77610,57 +76450,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -77753,50 +76542,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -77902,8 +76647,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -77993,8 +76741,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -78074,88 +76825,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -78224,8 +76893,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -78317,8 +76989,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -78659,23 +77334,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 1; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits; } - simdjson_inline int escape_index() { return 0; } - - bool escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - dst[0] = src[0]; - return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) }; -} - } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -78819,6 +77477,20 @@ simdjson_inline bool ends_with_partial_scalar(dom_parser_implementation &parser, e != ' ' && e != '\t' && e != '\n' && e != '\r'; } +/** + * The structural index past the last one of a partial batch is the end of this + * window, not a token of it: the byte there is the first byte of the next + * window. A finished value must not be rejected because that byte is ',', ':', + * ']' or '}'. A last document that itself ends with '{', '[', ',' or ':' is + * invalid JSON, yet its brackets may balance: walking it, the On-Demand API + * could read the next batch as a value. We leave such a document to the next + * batch, so that the batch ends between two documents. + */ +simdjson_inline bool ends_with_unfinished_document(dom_parser_implementation &parser) { + const uint8_t last = parser.buf[parser.structural_indexes[parser.n_structural_indexes - 1]]; + return last == '{' || last == '[' || last == ',' || last == ':'; +} + simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser, bool defer_last = false) { // Variant: do not count separately, just figure out depth if(parser.n_structural_indexes == 0) { return 0; } @@ -79652,7 +78324,7 @@ class json_iterator { * * - increment_count(iter) - each time a value is found in an array or object. */ - template + template simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; /** @@ -79669,8 +78341,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *peek() const noexcept; + template + simdjson_inline const uint8_t *peek(const uint32_t *batch_end = nullptr) const noexcept; /** * Advance to the next token. * @@ -79678,8 +78350,8 @@ class json_iterator { * * They may include invalid JSON as well (such as `1.2.3` or `ture`). */ - template - simdjson_inline const uint8_t *advance() noexcept; + template + simdjson_inline const uint8_t *advance(const uint32_t *batch_end = nullptr) noexcept; /** * Get the remaining length of the document, from the start of the current token. */ @@ -79727,7 +78399,7 @@ class json_iterator { simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; }; -template +template simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { logger::log_start(); @@ -79735,6 +78407,9 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Start the document // if (at_eof()) { return EMPTY; } + // Only used when BOUNDED (see advance()); a local, so that it can stay in a + // register, and vanishes from the other walks. + const uint32_t *const batch_end = &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; log_start_value("document"); SIMDJSON_TRY( visitor.visit_document_start(*this) ); @@ -79742,7 +78417,7 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & // Read first value // { - auto value = advance(); + auto value = advance(batch_end); // Make sure the outer object or array is closed before continuing; otherwise, there are ways we // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 @@ -79754,8 +78429,8 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & } switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; } } @@ -79772,29 +78447,29 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & SIMDJSON_TRY( visitor.visit_object_start(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.increment_count(*this) ); SIMDJSON_TRY( visitor.visit_key(*this, key) ); } object_field: - if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } + if (simdjson_unlikely( (*advance(batch_end) != ':') )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } object_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); { - auto key = advance(); + auto key = advance(batch_end); if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } SIMDJSON_TRY( visitor.visit_key(*this, key) ); } @@ -79822,16 +78497,16 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V & array_value: { - auto value = advance(); + auto value = advance(batch_end); switch (*value) { - case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; - case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; + case '{': if (*peek(batch_end) == '}') { advance(batch_end); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; + case '[': if (*peek(batch_end) == ']') { advance(batch_end); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; } } array_continue: - switch (*advance()) { + switch (*advance(batch_end)) { case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; default: log_error("Missing comma between array values"); return TAPE_ERROR; @@ -79861,8 +78536,18 @@ simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_par // Stage 1 leaves a sentinel index of value `len`; reading buf[len] requires // padding. For UNPADDED, return a dummy so walk_document can fail cleanly (#2815). -template -simdjson_inline const uint8_t *json_iterator::peek() const noexcept { +// +// When BOUNDED (see dom_parser_implementation::_bounded_batch), a document may +// not run past batch_end, one past the last structural index of the batch: +// past it, we return the same dummy, and walk_document fails cleanly. +template +simdjson_inline const uint8_t *json_iterator::peek(const uint32_t *batch_end) const noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -79872,8 +78557,14 @@ simdjson_inline const uint8_t *json_iterator::peek() const noexcept { } return &buf[idx]; } -template -simdjson_inline const uint8_t *json_iterator::advance() noexcept { +template +simdjson_inline const uint8_t *json_iterator::advance(const uint32_t *batch_end) noexcept { + SIMDJSON_IF_CONSTEXPR (BOUNDED) { + if (simdjson_unlikely(next_structural >= batch_end)) { + static constexpr uint8_t streaming_eof_sentinel = 0; + return &streaming_eof_sentinel; + } + } const uint32_t idx = *(next_structural++); SIMDJSON_IF_CONSTEXPR (UNPADDED) { if (simdjson_unlikely(idx >= dom_parser.len)) { @@ -80241,6 +78932,30 @@ struct tape_builder { return iter.walk_document(builder); } } + + // The stage 2 entry points of dom::document_stream, which picks one per batch + // (see internal::dom_parser_implementation::stage2_next_function). Out of + // line, so that stage2_next() can share the code. + simdjson_warn_unused static simdjson_never_inline error_code parse_next_document( + internal::dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + return parse_document(static_cast(dom_parser), doc); + } + + // A document of a stream batch that stage 2 must bound to its structural + // indexes (see internal::dom_parser_implementation::_bounded_batch). + simdjson_warn_unused static simdjson_never_inline error_code parse_next_bounded_document( + internal::dom_parser_implementation &_dom_parser, dom::document &doc) noexcept { + dom_parser_implementation &dom_parser = static_cast(_dom_parser); + dom_parser.doc = &doc; + json_iterator iter(dom_parser, dom_parser.next_structural_index); + if (dom_parser._unpadded) { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } else { + tape_builder_impl builder(doc); + return iter.walk_document(builder); + } + } }; template @@ -80804,8 +79519,14 @@ simdjson_warn_unused simdjson_inline error_code scan() { if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return CAPACITY; } } // We truncate the input to the end of the last complete document (or zero). - auto new_structural_indexes = find_next_document_index(parser, !unclosed_string && ends_with_partial_scalar(parser, len)); + // See json_structural_indexer.h for ends_with_unfinished_document(). + const bool partial_scalar = !unclosed_string && ends_with_partial_scalar(parser, len); + const bool unfinished = ends_with_unfinished_document(parser); + auto new_structural_indexes = find_next_document_index(parser, partial_scalar || unfinished); if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { + // The batch holds a single document, whose brackets balance but which + // cannot be valid JSON: it is not too big, it is invalid. + if (unfinished && find_next_document_index(parser, partial_scalar) != 0) { return TAPE_ERROR; } if(parser.structural_indexes[0] == 0) { // If the buffer is partial and we started at index 0 but the document is // incomplete, it's too big to parse. @@ -80863,6 +79584,9 @@ simdjson_warn_unused simdjson_inline error_code scan() { } parser.n_structural_indexes = new_structural_indexes; parser.structural_indexes[parser.n_structural_indexes] = next_batch_start; + // Repeat the RS that starts the next batch, as in json_structural_indexer.h: + // an invalid last document may make the On-Demand API skip it as a value. + parser.structural_indexes[parser.n_structural_indexes + 1] = next_batch_start; } else if (partial == stage1_mode::json_sequence_final) { // RFC 7464: final batch, last document extends to EOF // As above: a discarded unclosed string caps how far the filter may scan. @@ -81066,7 +79790,11 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document } simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - return stage2::tape_builder::parse_document(*this, _doc); + return stage2::tape_builder::parse_next_document(*this, _doc); +} + +internal::dom_parser_implementation::stage2_next_function_t dom_parser_implementation::stage2_next_function(bool bounded) const noexcept { + return bounded ? stage2::tape_builder::parse_next_bounded_document : stage2::tape_builder::parse_next_document; } SIMDJSON_NO_SANITIZE_MEMORY diff --git a/deps/simdjson/simdjson.h b/deps/simdjson/simdjson.h index faf21bdc2320..9d79c053f092 100644 --- a/deps/simdjson/simdjson.h +++ b/deps/simdjson/simdjson.h @@ -1,4 +1,4 @@ -/* auto-generated on 2026-10-14 15:49:28 -0400. version 5.0.5 Do not edit! */ +/* auto-generated on 2026-10-29 18:49:29 -0400. version 5.1.0 Do not edit! */ /* including simdjson.h: */ /* begin file simdjson.h */ #ifndef SIMDJSON_H @@ -2715,7 +2715,7 @@ namespace std { #define SIMDJSON_SIMDJSON_VERSION_H /** The version of simdjson being used (major.minor.revision) */ -#define SIMDJSON_VERSION "5.0.5" +#define SIMDJSON_VERSION "5.1.0" namespace simdjson { enum { @@ -2726,11 +2726,11 @@ enum { /** * The minor version (major.MINOR.revision) of simdjson being used. */ - SIMDJSON_VERSION_MINOR = 0, + SIMDJSON_VERSION_MINOR = 1, /** * The revision (major.minor.REVISION) of simdjson being used. */ - SIMDJSON_VERSION_REVISION = 5 + SIMDJSON_VERSION_REVISION = 0 }; } // namespace simdjson @@ -4007,6 +4007,23 @@ class dom_parser_implementation { */ simdjson_warn_unused virtual error_code stage2_next(dom::document &doc) noexcept = 0; + /** @private A stage 2 entry point for parser::parse_many: see stage2_next_function. */ + typedef error_code (*stage2_next_function_t)(dom_parser_implementation &impl, dom::document &doc); + + /** + * @private For internal implementation use + * + * Get a function that does what stage2_next() does. dom::document_stream picks + * it once per batch and calls it for each document of the batch, so that the + * choice between the bounded and the unchecked walk costs nothing per document. + * + * Overridden by each implementation. + * + * @param bounded Whether the documents must be bounded to the structural + * indexes of the batch (see _bounded_batch). + */ + virtual stage2_next_function_t stage2_next_function(bool bounded) const noexcept = 0; + /** * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There * must be an unescaped quote terminating the string. It returns the final output @@ -4131,6 +4148,14 @@ class dom_parser_implementation { */ bool _unpadded{false}; + /** + * Whether stage 2 must bound the documents of the batch indexed by the last + * stage 1 run to its structural indexes (dom::document_stream sets it, see + * document_stream::run_stage1, and passes it to stage2_next_function). Other + * batches keep the unchecked walk. + */ + bool _bounded_batch{false}; + protected: // Declaring these so that subclasses can use them to implement their constructors. @@ -4886,9 +4911,24 @@ class padded_string_view : public std::string_view { /** The number of allocated bytes. */ inline size_t capacity() const noexcept; - /** check that the view has sufficient padding */ + /** + * Check that the view has sufficient padding: either SIMDJSON_PADDING bytes of + * capacity past the end of the document, or enough trailing white space in + * the document to stand in for the missing bytes. + */ inline bool has_sufficient_padding() const noexcept; + /** + * The parser may read up to SIMDJSON_PADDING bytes past the end of the + * document. When the capacity falls short of that but the document ends + * with enough white space to make up for it (see has_sufficient_padding()), + * this moves that white space out of the document and into the padding, so + * that padding() >= SIMDJSON_PADDING. It does nothing otherwise. + * + * @return whether the document was shortened + */ + inline bool use_trailing_whitespace_as_padding() noexcept; + /** * Remove the UTF-8 Byte Order Mark (BOM) if it exists. * @@ -5090,6 +5130,14 @@ inline bool padded_string_view::has_sufficient_padding() const noexcept { return true; } +inline bool padded_string_view::use_trailing_whitespace_as_padding() noexcept { + if (padding() >= SIMDJSON_PADDING || !has_sufficient_padding()) { + return false; + } + remove_suffix(SIMDJSON_PADDING - padding()); + return true; +} + inline size_t padded_string_view::capacity() const noexcept { return _capacity; } inline size_t padded_string_view::padding() const noexcept { return capacity() - length(); } @@ -6166,7 +6214,6 @@ struct simdjson_result : public internal::simdjson_result_base at_pointer(std::string_view json_pointer) const noexcept; - inline void process_json_path_of_child_elements(std::vector::iterator& current, std::vector::iterator& end, const std::string_view& path_suffix, std::vector& accumulator) const noexcept; inline simdjson_result> at_path_with_wildcard(std::string_view json_path) const noexcept; inline simdjson_result at_path(std::string_view json_path) const noexcept; inline simdjson_result at(size_t index) const noexcept; @@ -6845,7 +6892,8 @@ class parser { inline simdjson_result parse_many(const uint8_t *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ inline simdjson_result parse_many(const char *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; - /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ + /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) + * Returns INSUFFICIENT_PADDING if s lacks SIMDJSON_PADDING bytes of spare capacity (or trailing whitespace). */ inline simdjson_result parse_many(const std::string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; inline simdjson_result parse_many(const std::string &&s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) = delete;// unsafe /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ @@ -7346,6 +7394,9 @@ class document_stream { /** Pass the next batch through stage 1 with the given parser. */ inline error_code run_stage1(dom::parser &p, size_t batch_start) noexcept; + /** Pick the stage 2 entry point for the batch that stage 1 just indexed. */ + inline void load_stage2() noexcept; + dom::parser *parser; const uint8_t *buf; size_t len; @@ -7356,6 +7407,8 @@ class document_stream { error_code error; size_t batch_start{0}; size_t doc_index{}; + /** The stage 2 entry point for the documents of the current batch (see load_stage2). */ + internal::dom_parser_implementation::stage2_next_function_t stage2_next_fn{nullptr}; #ifdef SIMDJSON_THREADS_ENABLED /** Indicates whether we use threads. Note that this needs to be a constant during the execution of the parsing. */ bool use_thread; @@ -8312,7 +8365,6 @@ struct simdjson_result : public internal::simdjson_result_base operator[](const char *key) const noexcept; simdjson_result operator[](int) const noexcept = delete; inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; - inline void process_json_path_of_child_elements(std::vector::iterator& current, std::vector::iterator& end, const std::string_view& path_suffix, std::vector& accumulator) const noexcept; inline simdjson_result> at_path_with_wildcard(std::string_view json_path_new) const noexcept; inline simdjson_result at_path(std::string_view json_path) const noexcept; inline simdjson_result at_key(std::string_view key) const noexcept; @@ -8532,6 +8584,11 @@ template class string_builder { simdjson_inline void append(simdjson::dom::key_value_pair value); private: + /** + * Append an element nested base_depth levels deep: the caller has already + * written the indentation (and key) before it. + **/ + inline void append(simdjson::dom::element value, size_t base_depth); formatter format{}; }; @@ -8945,16 +9002,6 @@ simdjson_inline bool is_matching_failure_error(error_code error) noexcept { return error == NO_SUCH_FIELD || error == INDEX_OUT_OF_BOUNDS || error == INCORRECT_TYPE || error == INVALID_JSON_POINTER; } - -/** - * Returns true if a wildcard follows in the remaining JSONPath. A segment that - * matches nothing before a wildcard yields an empty result instead of an - * error, as in DOM's at_path_with_wildcard. Like DOM, this treats a '*' inside - * a quoted key as a wildcard. - */ -simdjson_inline bool json_path_has_wildcard(std::string_view json_path) noexcept { - return json_path.find('*') != std::string_view::npos; -} } // namespace internal /** @@ -9077,6 +9124,53 @@ inline std::pair get_next_key_and_json_path( } namespace internal { +/** + * One level of a JSONPath as the On-Demand for_each_at_path_with_wildcard() + * consumes it. + */ +struct json_path_segment { + // The key or array index at this level, "*" for a wildcard. Empty if the + // path could not be parsed at this level. + std::string_view key{}; + // Whether a '*' occurs in the rest of the path, after this segment. A + // segment that matches nothing before a wildcard yields an empty result + // instead of an error, as in DOM's at_path_with_wildcard. Like DOM, this + // treats a '*' inside a quoted key as a wildcard. + bool wildcard_follows{false}; +}; + +/** + * Splits json_path with get_next_key_and_json_path(), one segment per level, + * as the On-Demand for_each_at_path_with_wildcard() historically did at each + * node. The keys point into json_path, which must outlive the result. + * + * Every node visited at a given depth sees the same remaining path, so we + * split it once up front: re-scanning the remaining path at every node costs + * O(nodes * path length), which made a long path after a wildcard take + * minutes on a large document. + * + * The last segment either has an empty key (the path is invalid there) or + * is the end of the path. + */ +inline std::vector split_json_path(std::string_view json_path) { + std::vector segments; + // The remaining path (always a suffix of json_path) contains a '*' if and + // only if it starts at or before the last one. + const size_t last_star = json_path.rfind('*'); + std::string_view path = json_path; + do { + std::pair key_and_json_path = get_next_key_and_json_path(path); + path = key_and_json_path.second; + const size_t offset = json_path.size() - path.size(); + json_path_segment segment; + segment.key = key_and_json_path.first; + segment.wildcard_follows = last_star != std::string_view::npos && last_star >= offset; + segments.push_back(segment); + if (segment.key.empty()) { break; } + } while (!path.empty()); + return segments; +} + /** * A JSONPath as the DOM at_path_with_wildcard() consumes it, parsed once. * @@ -9102,6 +9196,22 @@ struct wildcard_json_path { */ inline wildcard_json_path parse_wildcard_json_path(std::string_view json_path) { wildcard_json_path result; + // Each key is consumed by at_pointer, which unescapes ~0/~1, so '~' and + // '/' in the key must be escaped as json_path_to_pointer_conversion does: + // a raw '/' splits the step into two pointer tokens and a raw '~' + // invalidates it, either way addressing the wrong element. + const auto append_key = [](std::string &pointer, std::string_view key) { + pointer += '/'; + for (const char c : key) { + if (c == '~') { + pointer += "~0"; + } else if (c == '/') { + pointer += "~1"; + } else { + pointer += c; + } + } + }; // The remaining path (always a suffix of json_path) contains a '*' if and // only if it starts at or before the last one. const size_t last_star = json_path.rfind('*'); @@ -9117,7 +9227,24 @@ inline wildcard_json_path parse_wildcard_json_path(std::string_view json_path) { } const size_t offset = json_path.size() - path.size(); if (last_star == std::string_view::npos || last_star < offset) { - result.tail_pointer = json_path_to_pointer_conversion(path); + // Split the rest like the steps, so that a bracket-quoted key (['b'], + // ["b"]) is the key b, not 'b' with its quotes. What we do not split + // (e.g. [b], the key "b") is converted as at_path does. + std::string_view rest = path; + while (!rest.empty()) { + std::pair key_and_json_path = get_next_key_and_json_path(rest); + if (key_and_json_path.first.empty()) { + const std::string pointer = json_path_to_pointer_conversion(rest); + if (pointer == "-1") { + result.tail_pointer = pointer; + } else { + result.tail_pointer += pointer; + } + break; + } + append_key(result.tail_pointer, key_and_json_path.first); + rest = key_and_json_path.second; + } result.tail = (result.tail_pointer == "-1") ? wildcard_json_path::tail_type::invalid : wildcard_json_path::tail_type::pointer; @@ -9135,22 +9262,18 @@ inline wildcard_json_path parse_wildcard_json_path(std::string_view json_path) { } std::string step; step.reserve(key_and_json_path.first.size() + 1); - step += '/'; - // The step is consumed by at_pointer, which unescapes ~0/~1, so '~' and - // '/' in the key must be escaped as json_path_to_pointer_conversion does: - // a raw '/' splits the step into two pointer tokens and a raw '~' - // invalidates it, either way addressing the wrong element. - for (const char c : key_and_json_path.first) { - if (c == '~') { - step += "~0"; - } else if (c == '/') { - step += "~1"; - } else { - step += c; - } - } + append_key(step, key_and_json_path.first); result.steps.push_back(std::move(step)); path = key_and_json_path.second; + // The segment above consumed the path. An empty remainder is the node + // that segment selected (at_pointer("")), not a parse error. The check + // at the top of the loop is for a non-empty remainder that does not + // continue with '.' or '['. A '*' inside the final segment ($.a.b*, + // $['b*']) belongs to that segment. + if (path.empty()) { + result.tail = wildcard_json_path::tail_type::pointer; + return result; + } } } } // namespace internal @@ -9441,6 +9564,7 @@ inline simdjson_result simdjson_result::at(size_t inde } inline std::vector& simdjson_result::get_values(std::vector& out) const noexcept { + if (error()) { return out; } return first.get_values(out); } @@ -9690,6 +9814,7 @@ inline simdjson_result simdjson_result::at_key(std::s return first.at_key(key); } inline std::vector& simdjson_result::get_values(std::vector& out) const noexcept { + if (error()) { return out; } return first.get_values(out); } inline simdjson_result simdjson_result::at_key_case_insensitive(std::string_view key) const noexcept { @@ -10176,16 +10301,15 @@ inline void get_container_values(const dom::element &e, std::vector> at_wildcard_json_path(const dom::element &e, const wildcard_json_path &path, size_t step) noexcept { - const dom::element_type t = e.type(); - if (t != dom::element_type::ARRAY && t != dom::element_type::OBJECT) { - return std::vector{}; - } - std::vector children; + // A finished path names this node, even when it is a scalar. Checking the + // container type first would drop keys such as "b*" and "foo*". if (step == path.steps.size()) { switch (path.tail) { - case wildcard_json_path::tail_type::all_values: + case wildcard_json_path::tail_type::all_values: { + std::vector children; get_container_values(e, children); return children; + } case wildcard_json_path::tail_type::pointer: { dom::element result; auto error = e.at_pointer(path.tail_pointer).get(result); @@ -10198,6 +10322,11 @@ inline simdjson_result> at_wildcard_json_path(const do return INVALID_JSON_POINTER; } } + const dom::element_type t = e.type(); + if (t != dom::element_type::ARRAY && t != dom::element_type::OBJECT) { + return std::vector{}; + } + std::vector children; const std::string &key_pointer = path.steps[step]; if (key_pointer == "/*") { get_container_values(e, children); @@ -10477,10 +10606,16 @@ inline simdjson_result element::at_pointer(std::string_view json_pointe inline simdjson_result> element::at_path_with_wildcard(std::string_view json_path) const noexcept { SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 + const internal::wildcard_json_path path = internal::parse_wildcard_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.tail == internal::wildcard_json_path::tail_type::invalid) { + return INVALID_JSON_POINTER; + } switch (tape.tape_ref_type()) { case internal::tape_type::START_OBJECT: case internal::tape_type::START_ARRAY: - return internal::at_wildcard_json_path(*this, internal::parse_wildcard_json_path(json_path), 0); + return internal::at_wildcard_json_path(*this, path, 0); default: return std::vector{}; } @@ -10720,10 +10855,11 @@ inline simdjson_result parser::parse_into_document(document& provided_d inline simdjson_result parser::parse_into_document_unpadded(document& provided_doc, const uint8_t *buf, size_t len) & noexcept { // Like parse_into_document with realloc_if_needed=false (no copy, parse in - // place), but we tell the implementation the buffer is not padded so stage 2 - // avoids reading past buf+len: string unescaping is bounded, near-the-end + // place), but we tell the implementation the buffer is not padded so neither + // stage reads past buf+len: stage 1 copies the last block instead of loading + // it in place, and in stage 2 string unescaping is bounded, near-the-end // numbers are parsed from a padded copy, and atoms use length-aware - // validators (see tape_builder). Stage 1 is already safe for unpadded input. + // validators (see tape_builder). error_code _error = ensure_capacity(provided_doc, len); if (_error) { return _error; } @@ -10766,7 +10902,10 @@ simdjson_inline simdjson_result parser::parse(const padded_string &s) & return parse(s.data(), s.length(), false); } simdjson_inline simdjson_result parser::parse(const padded_string_view &v) & noexcept { - return parse(v.data(), v.length(), false); + if (!v.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + padded_string_view json(v); + json.use_trailing_whitespace_as_padding(); + return parse(json.data(), json.length(), false); } inline simdjson_result parser::parse_unpadded(const uint8_t *buf, size_t len) & noexcept { @@ -10786,13 +10925,16 @@ inline simdjson_result parser::parse_many(const char *buf, size return parse_many(reinterpret_cast(buf), len, batch_size); } inline simdjson_result parser::parse_many(const std::string &s, size_t batch_size) noexcept { - return parse_many(s.data(), s.length(), batch_size); + return parse_many(padded_string_view(s), batch_size); } inline simdjson_result parser::parse_many(const padded_string &s, size_t batch_size) noexcept { return parse_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::parse_many(const padded_string_view &v, size_t batch_size) noexcept { - return parse_many(v.data(), v.length(), batch_size); + if (!v.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + padded_string_view json(v); + json.use_trailing_whitespace_as_padding(); + return parse_many(json.data(), json.length(), batch_size); } inline simdjson_result parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept { @@ -10825,13 +10967,16 @@ inline simdjson_result parser::parse_many(const char *buf, size return parse_many(reinterpret_cast(buf), len, batch_size, format); } inline simdjson_result parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept { - return parse_many(s.data(), s.length(), batch_size, format); + return parse_many(padded_string_view(s), batch_size, format); } inline simdjson_result parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept { return parse_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::parse_many(const padded_string_view &v, size_t batch_size, stream_format format) noexcept { - return parse_many(v.data(), v.length(), batch_size, format); + if (!v.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + padded_string_view json(v); + json.use_trailing_whitespace_as_padding(); + return parse_many(json.data(), json.length(), batch_size, format); } simdjson_inline size_t parser::capacity() const noexcept { @@ -11103,6 +11248,7 @@ inline void document_stream::start() noexcept { error = run_stage1(*parser, batch_start); } if (error) { return; } + load_stage2(); #ifdef SIMDJSON_THREADS_ENABLED if (use_thread && next_batch_start() < len) { // Kick off the first thread if needed @@ -11179,7 +11325,7 @@ inline void document_stream::next() noexcept { // Load the next document from the batch doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; - error = parser->implementation->stage2_next(parser->doc); + error = stage2_next_fn(*parser->implementation, parser->doc); // If that was the last document in the batch, load another batch (if available) while (error == EMPTY) { batch_start = next_batch_start(); @@ -11195,9 +11341,10 @@ inline void document_stream::next() noexcept { error = run_stage1(*parser, batch_start); #endif if (error) { continue; } // If the error was EMPTY, we may want to load another batch. + load_stage2(); // Run stage 2 on the first document in the batch doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; - error = parser->implementation->stage2_next(parser->doc); + error = stage2_next_fn(*parser->implementation, parser->doc); } } inline size_t document_stream::size_in_bytes() const noexcept { @@ -11216,9 +11363,40 @@ inline size_t document_stream::next_batch_start() const noexcept { return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes]; } +// Stage 1 cuts the batch after the last document that looks complete, and +// leaves a sentinel structural index past it: stage 2 validates the documents, +// and stops an invalid one at the sentinel because the byte there cannot +// continue a document. That byte is not part of the batch, though: it is the +// start of the next document, padding (which the caller need not initialize) +// or the `]` that comma_delimited_array strips. When it is ',', ':', ']' or +// '}', or when the batch ends where a value is expected, an invalid document +// such as `[2` (before a truncated `[["`) could be accepted and run past the +// batch, into stale indexes such as the quote of an unclosed string, and so +// past the buffer. Stage 2 then walks this batch with an explicit bound (see +// load_stage2). Other batches keep the unchecked walk. +// +// set_bounded_batch() makes that decision for the batch stage 1 just indexed. +// Out of line: it runs once per batch, and inlined it would weigh on next(). +simdjson_never_inline void set_bounded_batch(internal::dom_parser_implementation &impl, const uint8_t *batch) noexcept { + bool bounded = false; + const uint32_t n = impl.n_structural_indexes; + if (n > 0) { + const uint8_t next = batch[impl.structural_indexes[n]]; + const uint8_t last = batch[impl.structural_indexes[n - 1]]; + bounded = next == ',' || next == ':' || next == ']' || next == '}' || + last == ',' || last == ':' || last == '[' || last == '{'; + } + impl._bounded_batch = bounded; +} + +inline void document_stream::load_stage2() noexcept { + stage2_next_fn = parser->implementation->stage2_next_function(parser->implementation->_bounded_batch); +} + inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept { size_t remaining = len - _batch_start; stage1_mode mode; + size_t batch_len; if (remaining <= batch_size) { // Final batch switch (format) { @@ -11232,7 +11410,7 @@ inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_star mode = stage1_mode::streaming_final; break; } - return p.implementation->stage1(&buf[_batch_start], remaining, mode); + batch_len = remaining; } else { // Partial batch switch (format) { @@ -11246,8 +11424,11 @@ inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_star mode = stage1_mode::streaming_partial; break; } - return p.implementation->stage1(&buf[_batch_start], batch_size, mode); + batch_len = batch_size; } + error_code err = p.implementation->stage1(&buf[_batch_start], batch_len, mode); + if (!err) { set_bounded_batch(*p.implementation, &buf[_batch_start]); } + return err; } #ifdef SIMDJSON_THREADS_ENABLED @@ -11258,7 +11439,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(*parser, stage1_thread_parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -11575,6 +11761,135 @@ inline bool document::dump_raw_tape(std::ostream &os) const noexcept { /* skipped duplicate #include "simdjson/dom/base.h" */ /* skipped duplicate #include "simdjson/dom/parser.h" */ /* skipped duplicate #include "simdjson/dom/serialization.h" */ +/* including simdjson/internal/jeaiii.h: #include "simdjson/internal/jeaiii.h" */ +/* begin file simdjson/internal/jeaiii.h */ +#ifndef SIMDJSON_INTERNAL_JEAIII_H +#define SIMDJSON_INTERNAL_JEAIII_H + +/* skipped duplicate #include "simdjson/base.h" */ +#include +#include + +namespace simdjson { +namespace internal { + +// Integer to decimal: James Edward Anhalt III's algorithm +static const char jeaiii_dd[201] = + "00010203040506070809101112131415161718192021222324252627282930313233343536373839" + "40414243444546474849505152535455565758596061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; +static const char jeaiii_fd[201] = + "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" + "10111213141516171819202122232425262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374757677787980818283848586878889" + "90919293949596979899"; + +simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { + std::memcpy(p, &jeaiii_dd[2 * k], 2); +} +simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { + std::memcpy(p, &jeaiii_fd[2 * k], 2); +} + +// Caller guarantees n < 10^8. Writes 1 to 8 digits. +simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { + constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; + constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; + if (n < 100) { + jeaiii_write_fd(b, n); + return n < 10 ? b + 1 : b + 2; + } + if (n < 1000000) { + if (n < 10000) { + const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; + jeaiii_write_fd(b, f0 >> 24); + b -= n < 1000; + const uint32_t f2 = uint32_t(f0 & mask24) * 100; + jeaiii_write_dd(b + 2, f2 >> 24); + return b + 4; + } + const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; + jeaiii_write_fd(b, f0 >> 32); + b -= n < 100000; + const uint64_t f2 = (f0 & mask32) * 100; + jeaiii_write_dd(b + 2, f2 >> 32); + const uint64_t f4 = (f2 & mask32) * 100; + jeaiii_write_dd(b + 4, f4 >> 32); + return b + 6; + } + const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; + jeaiii_write_fd(b, f0 >> 32); + b -= n < 10000000; + const uint64_t f2 = (f0 & mask32) * 100; + jeaiii_write_dd(b + 2, f2 >> 32); + const uint64_t f4 = (f2 & mask32) * 100; + jeaiii_write_dd(b + 4, f4 >> 32); + const uint64_t f6 = (f4 & mask32) * 100; + jeaiii_write_dd(b + 6, f6 >> 32); + return b + 8; +} + +// Caller guarantees z < 10^8. Always writes exactly 8 digits. +simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { + constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; + const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; + jeaiii_write_dd(b, f0 >> 32); + const uint64_t f2 = (f0 & mask32) * 100; + jeaiii_write_dd(b + 2, f2 >> 32); + const uint64_t f4 = (f2 & mask32) * 100; + jeaiii_write_dd(b + 4, f4 >> 32); + const uint64_t f6 = (f4 & mask32) * 100; + jeaiii_write_dd(b + 6, f6 >> 32); + return b + 8; +} + +// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. +simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { + constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; + const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; + jeaiii_write_fd(b, f0 >> 57); + b -= n < 1000000000; + const uint64_t f2 = (f0 & mask57) * 100; + jeaiii_write_dd(b + 2, f2 >> 57); + const uint64_t f4 = (f2 & mask57) * 100; + jeaiii_write_dd(b + 4, f4 >> 57); + const uint64_t f6 = (f4 & mask57) * 100; + jeaiii_write_dd(b + 6, f6 >> 57); + const uint64_t f8 = (f6 & mask57) * 100; + jeaiii_write_dd(b + 8, f8 >> 57); + return b + 10; +} + +simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { + if (n < 100000000) { + return jeaiii_lt1e8(b, uint32_t(n)); + } + if (n < (uint64_t(1) << 32)) { + return jeaiii_9_or_10(b, n); + } + // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. + const uint32_t z = uint32_t(n % 100000000); + uint64_t u = n / 100000000; + if (u < 100000000) { + // u has 2 to 8 digits (if u < 10, n would be below 2^32). + b = jeaiii_lt1e8(b, uint32_t(u)); + } else if (u < (uint64_t(1) << 32)) { + b = jeaiii_9_or_10(b, u); + } else { + // u has 11 or 12 digits: split off 8 more. + const uint32_t y = uint32_t(u % 100000000); + u /= 100000000; + b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits + b = jeaiii_8_digits(b, y); + } + return jeaiii_8_digits(b, z); +} + +} // namespace internal +} // namespace simdjson + +#endif // SIMDJSON_INTERNAL_JEAIII_H +/* end file simdjson/internal/jeaiii.h */ /* skipped duplicate #include "simdjson/internal/tape_type.h" */ /* skipped duplicate #include "simdjson/dom/array-inl.h" */ @@ -11660,75 +11975,6 @@ struct escape_sequence { const char string[7]; // technically, we only ever need 6 characters, we pad to 8 }; -/**@private - * This converts a signed integer into a character sequence. - * The caller is responsible for providing enough memory (at least - * 20 characters.) - * Though various runtime libraries provide itoa functions, - * it is not part of the C++ standard. The C++17 standard - * adds the to_chars functions which would do as well, but - * we want to support C++11. - */ -static char *fast_itoa(char *output, int64_t value) noexcept { - // This is a standard implementation of itoa. - char buffer[20]; - uint64_t value_positive; - // In general, negating a signed integer is unsafe. - if (value < 0) { - *output++ = '-'; - // Doing value_positive = -value; while avoiding - // undefined behavior warnings. - // It assumes two complement's which is universal at this - // point in time. - std::memcpy(&value_positive, &value, sizeof(value)); - value_positive = (~value_positive) + 1; // this is a negation - } else { - value_positive = value; - } - // We work solely with value_positive. It *might* be easier - // for an optimizing compiler to deal with an unsigned variable - // as far as performance goes. - const char *const end_buffer = buffer + 20; - char *write_pointer = buffer + 19; - // A faster approach is possible if we expect large integers: - // unroll the loop (work in 100s, 1000s) and use some kind of - // memoization. - while (value_positive >= 10) { - *write_pointer-- = char('0' + (value_positive % 10)); - value_positive /= 10; - } - *write_pointer = char('0' + value_positive); - size_t len = end_buffer - write_pointer; - std::memcpy(output, write_pointer, len); - return output + len; -} -/**@private - * This converts an unsigned integer into a character sequence. - * The caller is responsible for providing enough memory (at least - * 19 characters.) - * Though various runtime libraries provide itoa functions, - * it is not part of the C++ standard. The C++17 standard - * adds the to_chars functions which would do as well, but - * we want to support C++11. - */ -static char *fast_itoa(char *output, uint64_t value) noexcept { - // This is a standard implementation of itoa. - char buffer[20]; - const char *const end_buffer = buffer + 20; - char *write_pointer = buffer + 19; - // A faster approach is possible if we expect large integers: - // unroll the loop (work in 100s, 1000s) and use some kind of - // memoization. - while (value >= 10) { - *write_pointer-- = char('0' + (value % 10)); - value /= 10; - }; - *write_pointer = char('0' + value); - size_t len = end_buffer - write_pointer; - std::memcpy(output, write_pointer, len); - return output + len; -} - } // anonymous namespace namespace internal { @@ -11738,16 +11984,18 @@ namespace internal { template simdjson_inline void base_formatter::number(uint64_t x) { + // write_uint_jeaiii may store one byte before its output pointer. char number_buffer[24]; - char *newp = fast_itoa(number_buffer, x); - chars(number_buffer, newp); + chars(number_buffer + 1, write_uint_jeaiii(number_buffer + 1, x)); } template simdjson_inline void base_formatter::number(int64_t x) { char number_buffer[24]; - char *newp = fast_itoa(number_buffer, x); - chars(number_buffer, newp); + // 0 - uint64_t(x) is the magnitude, also for INT64_MIN. + char *end = write_uint_jeaiii(number_buffer + 1, x < 0 ? 0 - uint64_t(x) : uint64_t(x)); + number_buffer[0] = '-'; + chars(number_buffer + (x >= 0), end); } template @@ -11963,6 +12211,12 @@ simdjson_inline void pretty_formatter::print_space() { one_char(' '); } template inline void string_builder::append(simdjson::dom::element value) { + append(value, 0); + format.print_newline(); +} + +template +inline void string_builder::append(simdjson::dom::element value, size_t base_depth) { // using tape_type = simdjson::internal::tape_type; size_t depth = 0; constexpr size_t MAX_DEPTH = 16; @@ -11978,7 +12232,10 @@ inline void string_builder::append(simdjson::dom::element value) { format.print_newline(); } - format.print_indents(depth); + // At depth 0 the caller has already indented the value. + if (depth != 0) { + format.print_indents(base_depth + depth); + } // If we are in an object, print the next key and :, and skip to the next // value. @@ -11994,7 +12251,7 @@ inline void string_builder::append(simdjson::dom::element value) { // If we're too deep, we need to recurse to go deeper. depth++; if (simdjson_unlikely(depth >= MAX_DEPTH)) { - append(simdjson::dom::array(iter)); + append(simdjson::dom::element(iter), base_depth + depth - 1); iter.json_index = iter.matching_brace_index() - 1; // Jump to the ] depth--; break; @@ -12022,7 +12279,7 @@ inline void string_builder::append(simdjson::dom::element value) { // If we're too deep, we need to recurse to go deeper. depth++; if (simdjson_unlikely(depth >= MAX_DEPTH)) { - append(simdjson::dom::object(iter)); + append(simdjson::dom::element(iter), base_depth + depth - 1); iter.json_index = iter.matching_brace_index() - 1; // Jump to the } depth--; break; @@ -12094,7 +12351,7 @@ inline void string_builder::append(simdjson::dom::element value) { iter.tape_ref_type() == tape_type::END_OBJECT)) { format.print_newline(); depth--; - format.print_indents(depth); + format.print_indents(base_depth + depth); if (iter.tape_ref_type() == tape_type::END_ARRAY) { format.end_array(); } else { @@ -12105,38 +12362,16 @@ inline void string_builder::append(simdjson::dom::element value) { // Stop when we're at depth 0 } while (depth != 0); - - format.print_newline(); } template inline void string_builder::append(simdjson::dom::object value) { - format.start_object(); - auto pair = value.begin(); - auto end = value.end(); - if (pair != end) { - append(*pair); - for (++pair; pair != end; ++pair) { - format.comma(); - append(*pair); - } - } - format.end_object(); + append(simdjson::dom::element(value)); } template inline void string_builder::append(simdjson::dom::array value) { - format.start_array(); - auto iter = value.begin(); - auto end = value.end(); - if (iter != end) { - append(*iter); - for (++iter; iter != end; ++iter) { - format.comma(); - append(*iter); - } - } - format.end_array(); + append(simdjson::dom::element(value)); } template @@ -12348,11 +12583,17 @@ class structure_analyzer { const fractured_json_options& opts); /** Decide layout at the given render depth. Kept out of analysis since - * the same metrics can render inline or expanded at different depths. */ + * the same metrics can render inline or expanded at different depths. + * key_width is the width of the key, colon and colon padding written + * before the value on its first line (0 if the value has no key). */ static layout_mode decide_layout(const element_metrics& metrics, size_t depth, const fractured_json_options& opts, - bool has_trailing_comma = false); + bool has_trailing_comma = false, + size_t key_width = 0); + + /** Estimate inline length for a string (including quotes and escaping) */ + size_t estimate_string_length(std::string_view s) const; private: const fractured_json_options* current_opts_ = nullptr; @@ -12369,9 +12610,6 @@ class structure_analyzer { /** Analyze an object element */ element_metrics analyze_object(const dom::object& obj, size_t depth) const; - /** Estimate inline length for a string (including quotes and escaping) */ - size_t estimate_string_length(std::string_view s) const; - /** Estimate inline length for a number */ size_t estimate_number_length(double d) const; size_t estimate_number_length(int64_t i) const; @@ -12516,11 +12754,14 @@ class fractured_string_builder { /** Format an element using pre-computed metrics */ void format_element(const dom::element& elem, const element_metrics& metrics, size_t depth, - bool has_trailing_comma = false); + bool has_trailing_comma = false, size_t key_width = 0); /** Format an array with the appropriate layout */ void format_array(const dom::array& arr, const element_metrics& metrics, size_t depth, - bool has_trailing_comma = false); + bool has_trailing_comma = false, size_t key_width = 0); + + /** Format an element on a single line, whatever its depth */ + void format_element_inline(const dom::element& elem, const element_metrics& metrics); /** Format an array inline: [1, 2, 3] */ void format_array_inline(const dom::array& arr, const element_metrics& metrics); @@ -12572,7 +12813,7 @@ class fractured_string_builder { /** Format an object with the appropriate layout */ void format_object(const dom::object& obj, const element_metrics& metrics, size_t depth, - bool has_trailing_comma = false); + bool has_trailing_comma = false, size_t key_width = 0); /** Format an object inline: {"a": 1, "b": 2} */ void format_object_inline(const dom::object& obj, const element_metrics& metrics); @@ -12686,6 +12927,13 @@ inline element_metrics structure_analyzer::analyze_scalar(const dom::element& el } break; } + case dom::element_type::BIGINT: { + std::string_view digits; + if (elem.get_bigint().get(digits) == SUCCESS) { + metrics.estimated_inline_len = digits.size(); + } + break; + } case dom::element_type::BOOL: { bool val; if (elem.get_bool().get(val) == SUCCESS) { @@ -12828,10 +13076,14 @@ inline size_t structure_analyzer::estimate_number_length(double d) const { return 4; // "null" for invalid numbers #endif } - // Rough estimate: up to 17 significant digits + sign + decimal point + exponent - char buf[32]; - int len = snprintf(buf, sizeof(buf), "%.17g", d); - return len > 0 ? static_cast(len) : 20; + // Must match base_formatter::number(double), which serializes finite values + // with internal::to_chars (shortest round-trip). snprintf("%.17g") over-counts + // (e.g. 0.1 becomes "0.10000000000000001", 19 chars, vs the emitted "0.1"), + // which misaligns double columns in table formatting the same way escaped + // control characters did before #2929. + char buf[simdjson::internal::to_chars_buffer_size]; + char *end = simdjson::internal::to_chars(buf, buf + sizeof(buf), d); + return static_cast(end - buf); } inline size_t structure_analyzer::estimate_number_length(int64_t i) const { @@ -12863,7 +13115,8 @@ inline table_column_type structure_analyzer::classify_table_value(dom::element_t case dom::element_type::ARRAY: return table_column_type::array; case dom::element_type::INT64: case dom::element_type::UINT64: - case dom::element_type::DOUBLE: return table_column_type::number; + case dom::element_type::DOUBLE: + case dom::element_type::BIGINT: return table_column_type::number; case dom::element_type::NULL_VALUE: return table_column_type::unknown; default: return table_column_type::simple; // string, bool } @@ -13089,7 +13342,8 @@ inline void structure_analyzer::recompute_column_widths(std::vector= opts.always_expand_depth; // Check inline feasibility - size_t reserved_width = depth * opts.indent_spaces + (has_trailing_comma ? 1 : 0); + size_t reserved_width = depth * opts.indent_spaces + key_width + + (has_trailing_comma ? 1 : 0); if (depth_allows_inline_or_compact && metrics.can_inline && metrics.estimated_inline_len + reserved_width <= opts.max_total_line_length) { return layout_mode::single_line; @@ -13234,19 +13489,20 @@ simdjson_inline std::string_view fractured_string_builder::str() const { inline void fractured_string_builder::format_element(const dom::element& elem, const element_metrics& metrics, size_t depth, - bool has_trailing_comma) { + bool has_trailing_comma, + size_t key_width) { switch (elem.type()) { case dom::element_type::ARRAY: { dom::array arr; if (elem.get_array().get(arr) == SUCCESS) { - format_array(arr, metrics, depth, has_trailing_comma); + format_array(arr, metrics, depth, has_trailing_comma, key_width); } break; } case dom::element_type::OBJECT: { dom::object obj; if (elem.get_object().get(obj) == SUCCESS) { - format_object(obj, metrics, depth, has_trailing_comma); + format_object(obj, metrics, depth, has_trailing_comma, key_width); } break; } @@ -13259,8 +13515,10 @@ inline void fractured_string_builder::format_element(const dom::element& elem, inline void fractured_string_builder::format_array(const dom::array& arr, const element_metrics& metrics, size_t depth, - bool has_trailing_comma) { - layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, has_trailing_comma); + bool has_trailing_comma, + size_t key_width) { + layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, + has_trailing_comma, key_width); switch (layout) { case layout_mode::single_line: format_array_inline(arr, metrics); @@ -13278,6 +13536,33 @@ inline void fractured_string_builder::format_array(const dom::array& arr, } } +// The children of an inline value are written inline as well. Going through +// format_element() would let decide_layout() pick another layout for them +// (e.g. at a depth not past always_expand_depth), which a single line cannot +// show: it would only add table padding and drop the comma padding. +inline void fractured_string_builder::format_element_inline(const dom::element& elem, + const element_metrics& metrics) { + switch (elem.type()) { + case dom::element_type::ARRAY: { + dom::array arr; + if (elem.get_array().get(arr) == SUCCESS) { + format_array_inline(arr, metrics); + } + break; + } + case dom::element_type::OBJECT: { + dom::object obj; + if (elem.get_object().get(obj) == SUCCESS) { + format_object_inline(obj, metrics); + } + break; + } + default: + format_scalar(elem); + break; + } +} + inline void fractured_string_builder::format_array_inline(const dom::array& arr, const element_metrics& metrics) { scoped_single_line_mode single_line(format_); @@ -13299,7 +13584,7 @@ inline void fractured_string_builder::format_array_inline(const dom::array& arr, } first = false; const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx); - format_element(elem, child_metrics, 0); + format_element_inline(elem, child_metrics); child_idx++; } @@ -13327,14 +13612,19 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom:: for (dom::element elem : arr) { const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx); + bool is_last = (child_idx + 1 == metrics.child_count); if (!first) { format_.comma(); format_.track_line_length(1); - // Check if we should break to new line + // Check if we should break to new line. Staying on this line also + // takes the padding space before the item and the comma after it. + size_t upcoming_length = child_metrics.estimated_inline_len + + (options_.comma_padding ? 1 : 0) + + (is_last ? 0 : 1); if (prev_item_was_expanded || - format_.should_break_line(child_metrics.estimated_inline_len)) { + format_.should_break_line(upcoming_length)) { format_.print_newline(); format_.print_indents(depth + 1); } else if (options_.comma_padding) { @@ -13343,7 +13633,6 @@ inline void fractured_string_builder::format_array_compact_multiline(const dom:: } first = false; - bool is_last = (child_idx + 1 == metrics.child_count); layout_mode item_layout = structure_analyzer::decide_layout(child_metrics, depth + 1, options_, !is_last); bool item_fits = item_layout == layout_mode::single_line; if (item_fits) { @@ -13710,8 +13999,10 @@ inline void fractured_string_builder::format_array_expanded(const dom::array& ar inline void fractured_string_builder::format_object(const dom::object& obj, const element_metrics& metrics, size_t depth, - bool has_trailing_comma) { - layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, has_trailing_comma); + bool has_trailing_comma, + size_t key_width) { + layout_mode layout = structure_analyzer::decide_layout(metrics, depth, options_, + has_trailing_comma, key_width); if (layout == layout_mode::single_line) { format_object_inline(obj, metrics); } else { @@ -13746,7 +14037,7 @@ inline void fractured_string_builder::format_object_inline(const dom::object& ob format_.print_space(); } const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx); - format_element(field.value, child_metrics, 0); + format_element_inline(field.value, child_metrics); child_idx++; } @@ -13780,7 +14071,10 @@ inline void fractured_string_builder::format_object_expanded(const dom::object& } const element_metrics& child_metrics = child_metrics_at(metrics.children, child_idx); bool is_last = (child_idx + 1 == metrics.child_count); - format_element(field.value, child_metrics, depth + 1, !is_last); + // An inline value shares its line with the key, the colon and its padding. + size_t key_width = analyzer_.estimate_string_length(field.key) + 1 + + (options_.colon_padding ? 1 : 0); + format_element(field.value, child_metrics, depth + 1, !is_last, key_width); child_idx++; } @@ -13821,6 +14115,14 @@ inline void fractured_string_builder::format_scalar(const dom::element& elem) { } break; } + case dom::element_type::BIGINT: { + // Big integer stored as string: output the raw digits (no quotes) + std::string_view digits; + if (elem.get_bigint().get(digits) == SUCCESS) { + format_.chars(digits.data(), digits.data() + digits.size()); + } + break; + } case dom::element_type::BOOL: { bool val; if (elem.get_bool().get(val) == SUCCESS) { @@ -16243,31 +16545,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin }; } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - (is_backslash | is_quote | is_control).to_bitmask64() - }; -} - - } // unnamed namespace } // namespace arm64 @@ -16661,6 +16938,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -17422,21 +17700,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -17982,57 +18245,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -18125,50 +18337,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -18274,8 +18442,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -18365,8 +18536,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -18446,88 +18620,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -18596,8 +18688,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -18689,8 +18784,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -19000,23 +19098,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 1; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits; } - simdjson_inline int escape_index() { return 0; } - - bool escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - dst[0] = src[0]; - return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) }; -} - } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -19496,6 +19577,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -20257,21 +20339,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -20817,57 +20884,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -20960,50 +20976,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -21109,8 +21081,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -21200,8 +21175,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -21281,88 +21259,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -21431,8 +21327,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -21524,8 +21423,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -22394,30 +22296,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 32; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace haswell } // namespace simdjson @@ -22808,6 +22686,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -23569,21 +23448,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -24129,57 +23993,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -24272,50 +24085,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -24421,8 +24190,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -24512,8 +24284,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -24593,88 +24368,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -24743,8 +24436,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -24836,8 +24532,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -25642,34 +25341,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } - -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 64; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(uint64_t(escape_bits)); } - - __mmask64 escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - __mmask64 is_quote = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('"')); - __mmask64 is_backslash = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('\\')); - __mmask64 is_control = _mm512_cmplt_epi8_mask(v, _mm512_set1_epi8(32)); - return { - (is_backslash | is_quote | is_control) - }; -} - - - - } // unnamed namespace } // namespace icelake } // namespace simdjson @@ -26120,6 +25791,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -26881,21 +26553,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -27441,57 +27098,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -27584,50 +27190,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -27733,8 +27295,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -27824,8 +27389,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -27905,88 +27473,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -28055,8 +27541,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -28148,8 +27637,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -29130,31 +28622,6 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - // We store it as a 64-bit bitmask even though we only need 16 bits. - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace ppc64 } // namespace simdjson @@ -29547,6 +29014,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -30308,21 +29776,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -30868,57 +30321,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -31011,50 +30413,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -31160,8 +30518,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -31251,8 +30612,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -31332,88 +30696,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -31482,8 +30764,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -31575,8 +30860,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -32867,30 +32155,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace westmere } // namespace simdjson @@ -33281,6 +32545,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -34042,21 +33307,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -34602,57 +33852,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -34745,50 +33944,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -34894,8 +34049,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -34985,8 +34143,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -35066,88 +34227,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -35216,8 +34295,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -35309,8 +34391,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -36113,30 +35198,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lasx } // namespace simdjson @@ -36531,6 +35592,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -37292,21 +36354,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -37852,57 +36899,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -37995,50 +36991,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -38144,8 +37096,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -38235,8 +37190,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -38316,88 +37274,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -38466,8 +37342,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -38559,8 +37438,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -39343,30 +38225,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lsx } // namespace simdjson @@ -39759,6 +38617,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -40520,21 +39379,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -41080,57 +39924,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -41223,50 +40016,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -41372,8 +40121,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -41463,8 +40215,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -41544,88 +40299,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -41694,8 +40367,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -41787,8 +40463,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -42534,23 +41213,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin return { (v == '\\').to_bitmask(), (v == '"').to_bitmask() }; } -struct escaping { - static constexpr uint64_t BYTES_PROCESSED = sizeof(simd8); - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - return { ((v == '"') | (v == '\\') | (v == 32)).to_bitmask() }; -} - } // unnamed namespace } // namespace rvv_vls @@ -43003,6 +41665,7 @@ class dom_parser_implementation final : public internal::dom_parser_implementati simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + stage2_next_function_t stage2_next_function(bool bounded) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; @@ -43764,21 +42427,6 @@ static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); } -static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { - *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); - // We do not accept infinite values. - - // Detecting finite values in a portable manner is ridiculously hard, ideally - // we would want to do: - // return !std::isfinite(*outDouble); - // but that mysteriously fails under legacy/old libc++ libraries, see - // https://github.com/simdjson/simdjson/issues/1286 - // - // Therefore, fall back to this solution (the extra parens are there - // to handle that max may be a macro on windows). - return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); -} - // Same as parse_float_fallback, but for binary32 (float) values. Going through // the binary64 fallback and then rounding to binary32 would be subject to // double rounding, so we run the fallback algorithm directly on binary32. @@ -44324,57 +42972,6 @@ simdjson_unused simdjson_inline simdjson_result parse_unsigned(const u return i; } - - -// Parse any number from 0 to 18,446,744,073,709,551,615 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { - const uint8_t *p = src; - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // The longest positive 64-bit number is 20 digits. - // We do it this way so we don't trigger this branch unless we must. - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > 20)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - - if (digit_count == 20) { - // Positive overflow check: - // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the - // biggest uint64_t. - // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. - // If we got here, it's a 20 digit number starting with the digit "1". - // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller - // than 1,553,255,926,290,448,384. - // - That is smaller than the smallest possible 20-digit number the user could write: - // 10,000,000,000,000,000,000. - // - Therefore, if the number is positive and lower than that, it's overflow. - // - The value we are looking at is less than or equal to INT64_MAX. - // - if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } - } - - return i; -} - // Parse any number from 0 to 18,446,744,073,709,551,615 simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { const uint8_t *p = src + 1; @@ -44467,50 +43064,6 @@ simdjson_unused simdjson_inline simdjson_result parse_integer(const uin return negative ? (~i+1) : i; } -// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { - // - // Check for minus sign - // - if(src == src_end) { return NUMBER_ERROR; } - bool negative = (*src == '-'); - const uint8_t *p = src + uint8_t(negative); - - // - // Parse the integer part. - // - // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare - const uint8_t *const start_digits = p; - uint64_t i = 0; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - - // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. - // Optimization note: size_t is expected to be unsigned. - size_t digit_count = size_t(p - start_digits); - // We go from - // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 - // so we can never represent numbers that have more than 19 digits. - size_t longest_digit_count = 19; - // Optimization note: the compiler can probably merge - // ((digit_count == 0) || (digit_count > longest_digit_count)) - // into a single branch since digit_count is unsigned. - if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } - // Here digit_count > 0. - if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } - // We can do the following... - // if (!jsoncharutils::is_structural_or_whitespace(*p)) { - // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; - // } - // as a single table lookup: - if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } - // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. - // Performance note: This check is only needed when digit_count == longest_digit_count but it is - // so cheap that we might as well always make it. - if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } - return negative ? (~i+1) : i; -} - // Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { // @@ -44616,8 +43169,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double(const uint8 uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -44707,8 +43263,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float(const uint8_t uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -44788,88 +43347,6 @@ simdjson_unused simdjson_inline simdjson_result get_number_type(con return number_type::floating_point_number; } -// Never read at src_end or beyond -simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { - if(src == src_end) { return NUMBER_ERROR; } - // - // Check for minus sign - // - bool negative = (*src == '-'); - src += uint8_t(negative); - - // - // Parse the integer part. - // - uint64_t i = 0; - const uint8_t *p = src; - if(p == src_end) { return NUMBER_ERROR; } - p += parse_digit(*p, i); - bool leading_zero = (i == 0); - while ((p != src_end) && parse_digit(*p, i)) { p++; } - // no integer digits, or 0123 (zero must be solo) - if ( p == src ) { return INCORRECT_TYPE; } - if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } - - // - // Parse the decimal part. - // - int64_t exponent = 0; - bool overflow; - if (simdjson_likely((p != src_end) && (*p == '.'))) { - p++; - const uint8_t *start_decimal_digits = p; - if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits - p++; - while ((p != src_end) && parse_digit(*p, i)) { p++; } - exponent = -(p - start_decimal_digits); - - // Overflow check. More than 19 digits (minus the decimal) may be overflow. - overflow = p-src-1 > 19; - if (simdjson_unlikely(overflow && leading_zero)) { - // Skip leading 0.00000 and see if it still overflows - const uint8_t *start_digits = src + 2; - while (*start_digits == '0') { start_digits++; } - overflow = start_digits-src > 19; - } - } else { - overflow = p-src > 19; - } - - // - // Parse the exponent - // - if ((p != src_end) && (*p == 'e' || *p == 'E')) { - p++; - if(p == src_end) { return NUMBER_ERROR; } - bool exp_neg = *p == '-'; - p += exp_neg || *p == '+'; - - uint64_t exp = 0; - const uint8_t *start_exp_digits = p; - while ((p != src_end) && parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } - - exponent += exp_neg ? 0-exp : exp; - } - - if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } - - overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; - - // - // Assemble (or slow-parse) the float - // - double d; - if (simdjson_likely(!overflow)) { - if (compute_float_64(exponent, i, negative, d)) { return d; } - } - if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { - return NUMBER_ERROR; - } - return d; -} - simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { // // Check for minus sign @@ -44938,8 +43415,11 @@ simdjson_unused simdjson_inline simdjson_result parse_double_in_string(c uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -45031,8 +43511,11 @@ simdjson_unused simdjson_inline simdjson_result parse_float_in_string(con uint64_t exp = 0; const uint8_t *start_exp_digits = p; while (parse_digit(*p, exp)) { p++; } - // no exp digits, or 20+ exp digits - if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } + // no exp digits + if (p-start_exp_digits == 0) { return NUMBER_ERROR; } + // 20+ exp digits (e.g., 0e9999999999999999999999) are valid, but exp may + // have wrapped around: leave such numbers to the fallback parser. + overflow = overflow || p-start_exp_digits > 19; exponent += exp_neg ? 0-exp : exp; } @@ -45606,6 +44089,7 @@ consteval const char* get_json_key_name() { #endif // SIMDJSON_STATIC_REFLECTION #endif // SIMDJSON_ANNOTATIONS_H /* end file simdjson/annotations.h */ +/* skipped duplicate #include "simdjson/internal/jeaiii.h" */ #endif // SIMDJSON_GENERIC_BUILDER_DEPENDENCIES_H /* end file simdjson/generic/builder/dependencies.h */ @@ -46457,31 +44941,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin }; } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - (is_backslash | is_quote | is_control).to_bitmask64() - }; -} - - } // unnamed namespace } // namespace arm64 @@ -46553,6 +45012,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -46566,6 +45029,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -46744,6 +45219,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -46763,7 +45252,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -46804,7 +45293,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -46822,20 +45317,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::arm64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::arm64::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::arm64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::arm64::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -46851,7 +45342,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -46877,7 +45368,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -47010,6 +45501,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -47193,6 +45729,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -47231,6 +45769,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -47242,17 +45805,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -47352,25 +45909,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -47408,6 +45970,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -47452,6 +46019,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -47586,10 +46156,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -47652,9 +46219,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -47676,16 +46243,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = arm64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { arm64::builder::string_builder b(initial_capacity); arm64::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for arm64 */ /* including simdjson/generic/builder/fractured_json_builder.h for arm64: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for arm64 */ @@ -47824,6 +46391,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -47936,6 +46504,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -47951,82 +46525,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -48199,6 +46698,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -48226,10 +46726,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -48510,10 +47006,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -48536,22 +47043,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc + set_valid(false); + return; + } +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -48563,7 +47111,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -48571,7 +47119,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -48581,124 +47129,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -48726,20 +47164,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -48749,9 +47189,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -48765,11 +47205,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -48785,16 +47225,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -48802,9 +47242,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -48818,7 +47258,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -48831,9 +47271,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -48841,10 +47281,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -48874,7 +47314,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -48882,7 +47322,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -48890,7 +47330,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -48900,7 +47340,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -48994,54 +47434,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -49310,23 +47750,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 1; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits; } - simdjson_inline int escape_index() { return 0; } - - bool escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - dst[0] = src[0]; - return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) }; -} - } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -49484,6 +47907,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -49497,6 +47924,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -49675,6 +48114,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -49694,7 +48147,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -49735,7 +48188,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -49753,20 +48212,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::fallback::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::fallback::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::fallback::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::fallback::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -49782,7 +48237,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -49808,7 +48263,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -49941,6 +48396,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -50124,6 +48624,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -50162,6 +48664,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -50173,17 +48700,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -50283,25 +48804,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -50339,6 +48865,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -50383,6 +48914,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -50517,10 +49051,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -50583,9 +49114,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -50607,16 +49138,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = fallback::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { fallback::builder::string_builder b(initial_capacity); fallback::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for fallback */ /* including simdjson/generic/builder/fractured_json_builder.h for fallback: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for fallback */ @@ -50755,6 +49286,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -50867,6 +49399,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -50882,82 +49420,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -51130,6 +49593,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -51157,10 +49621,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -51441,10 +49901,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -51467,22 +49938,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc + set_valid(false); + return; + } +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -51494,7 +50006,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -51502,7 +50014,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -51512,124 +50024,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -51657,20 +50059,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -51680,9 +50084,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -51696,11 +50100,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -51716,16 +50120,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -51733,9 +50137,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -51749,7 +50153,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -51762,9 +50166,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -51772,10 +50176,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -51805,7 +50209,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -51813,7 +50217,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -51821,7 +50225,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -51831,7 +50235,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -51925,54 +50329,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -52800,30 +51204,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 32; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace haswell } // namespace simdjson @@ -52892,6 +51272,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -52905,6 +51289,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -53083,6 +51479,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -53102,7 +51512,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -53143,7 +51553,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -53161,20 +51577,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::haswell::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::haswell::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::haswell::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::haswell::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -53190,7 +51602,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -53216,7 +51628,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -53349,6 +51761,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -53532,6 +51989,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -53570,6 +52029,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -53581,17 +52065,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -53691,25 +52169,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -53747,6 +52230,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -53791,6 +52279,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -53925,10 +52416,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -53991,9 +52479,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -54015,16 +52503,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = haswell::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { haswell::builder::string_builder b(initial_capacity); haswell::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for haswell */ /* including simdjson/generic/builder/fractured_json_builder.h for haswell: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for haswell */ @@ -54163,6 +52651,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -54275,6 +52764,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -54290,82 +52785,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -54538,6 +52958,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -54565,10 +52986,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -54849,10 +53266,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -54875,22 +53303,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -54902,7 +53371,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -54910,7 +53379,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -54920,124 +53389,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -55065,20 +53424,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -55088,9 +53449,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -55104,11 +53465,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -55124,16 +53485,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -55141,9 +53502,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -55157,7 +53518,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -55170,9 +53531,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -55180,10 +53541,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -55213,7 +53574,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -55221,7 +53582,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -55229,7 +53590,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -55239,7 +53600,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -55333,54 +53694,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -56144,34 +54505,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } - -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 64; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(uint64_t(escape_bits)); } - - __mmask64 escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - __mmask64 is_quote = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('"')); - __mmask64 is_backslash = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('\\')); - __mmask64 is_control = _mm512_cmplt_epi8_mask(v, _mm512_set1_epi8(32)); - return { - (is_backslash | is_quote | is_control) - }; -} - - - - } // unnamed namespace } // namespace icelake } // namespace simdjson @@ -56300,6 +54633,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -56313,6 +54650,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -56491,6 +54840,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -56510,7 +54873,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -56551,7 +54914,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -56569,20 +54938,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::icelake::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::icelake::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::icelake::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::icelake::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -56598,7 +54963,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -56624,7 +54989,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -56757,6 +55122,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -56940,6 +55350,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -56978,6 +55390,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -56989,17 +55426,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -57099,25 +55530,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -57155,6 +55591,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -57199,6 +55640,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -57333,10 +55777,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -57399,9 +55840,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -57423,16 +55864,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = icelake::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { icelake::builder::string_builder b(initial_capacity); icelake::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for icelake */ /* including simdjson/generic/builder/fractured_json_builder.h for icelake: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for icelake */ @@ -57571,6 +56012,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -57683,6 +56125,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -57698,82 +56146,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -57946,6 +56319,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -57973,10 +56347,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -58257,10 +56627,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -58283,22 +56664,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc + set_valid(false); + return; + } +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -58310,7 +56732,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -58318,7 +56740,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -58328,124 +56750,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -58473,20 +56785,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -58496,9 +56810,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -58512,11 +56826,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -58532,16 +56846,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -58549,9 +56863,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -58565,7 +56879,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -58578,9 +56892,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -58588,10 +56902,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -58621,7 +56935,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -58629,7 +56943,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -58637,7 +56951,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -58647,7 +56961,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -58741,54 +57055,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -59728,31 +58042,6 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - // We store it as a 64-bit bitmask even though we only need 16 bits. - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace ppc64 } // namespace simdjson @@ -59823,6 +58112,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -59836,6 +58129,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -60014,6 +58319,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -60033,7 +58352,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -60074,7 +58393,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -60092,20 +58417,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::ppc64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::ppc64::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::ppc64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::ppc64::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -60121,7 +58442,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -60147,7 +58468,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -60280,6 +58601,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -60463,6 +58829,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -60501,6 +58869,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -60512,17 +58905,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -60622,25 +59009,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -60678,6 +59070,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -60722,6 +59119,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -60856,10 +59256,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -60922,9 +59319,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -60946,16 +59343,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = ppc64::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { ppc64::builder::string_builder b(initial_capacity); ppc64::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for ppc64 */ /* including simdjson/generic/builder/fractured_json_builder.h for ppc64: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for ppc64 */ @@ -61094,6 +59491,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -61206,6 +59604,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -61221,82 +59625,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -61469,6 +59798,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -61496,10 +59826,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -61780,10 +60106,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -61806,22 +60143,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -61833,7 +60211,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -61841,7 +60219,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -61851,124 +60229,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -61996,20 +60264,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -62019,9 +60289,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -62035,11 +60305,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -62055,16 +60325,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -62072,9 +60342,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -62088,7 +60358,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -62101,9 +60371,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -62111,10 +60381,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -62144,7 +60414,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -62152,7 +60422,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -62160,7 +60430,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -62170,7 +60440,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -62264,54 +60534,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -63561,30 +61831,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace westmere } // namespace simdjson @@ -63653,6 +61899,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -63666,6 +61916,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -63844,6 +62106,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -63863,7 +62139,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -63904,7 +62180,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -63922,20 +62204,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::westmere::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::westmere::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::westmere::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::westmere::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -63951,7 +62229,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -63977,7 +62255,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -64110,6 +62388,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -64293,6 +62616,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -64331,6 +62656,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -64342,17 +62692,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -64452,25 +62796,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -64508,6 +62857,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -64552,6 +62906,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -64686,10 +63043,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -64752,9 +63106,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -64776,16 +63130,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = westmere::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { westmere::builder::string_builder b(initial_capacity); westmere::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for westmere */ /* including simdjson/generic/builder/fractured_json_builder.h for westmere: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for westmere */ @@ -64924,6 +63278,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -65036,6 +63391,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -65051,82 +63412,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -65299,6 +63585,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -65326,10 +63613,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -65610,10 +63893,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -65636,22 +63930,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -65663,7 +63998,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -65671,7 +64006,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -65681,124 +64016,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -65826,20 +64051,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -65849,9 +64076,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -65865,11 +64092,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -65885,16 +64112,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -65902,9 +64129,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -65918,7 +64145,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -65931,9 +64158,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -65941,10 +64168,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -65974,7 +64201,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -65982,7 +64209,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -65990,7 +64217,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -66000,7 +64227,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -66094,54 +64321,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -66879,30 +65106,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lsx } // namespace simdjson @@ -66973,6 +65176,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -66986,6 +65193,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -67164,6 +65383,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -67183,7 +65416,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -67224,7 +65457,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -67242,20 +65481,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::lsx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::lsx::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::lsx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::lsx::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -67271,7 +65506,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -67297,7 +65532,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -67430,6 +65665,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -67613,6 +65893,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -67651,6 +65933,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -67662,17 +65969,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -67772,25 +66073,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -67828,6 +66134,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -67872,6 +66183,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -68006,10 +66320,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -68072,9 +66383,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -68096,16 +66407,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = lsx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { lsx::builder::string_builder b(initial_capacity); lsx::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for lsx */ /* including simdjson/generic/builder/fractured_json_builder.h for lsx: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for lsx */ @@ -68244,6 +66555,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -68356,6 +66668,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -68371,82 +66689,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -68619,6 +66862,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -68646,10 +66890,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -68930,10 +67170,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -68956,22 +67207,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -68983,7 +67275,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -68991,7 +67283,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -69001,124 +67293,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -69146,20 +67328,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -69169,9 +67353,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -69185,11 +67369,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -69205,16 +67389,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -69222,9 +67406,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -69238,7 +67422,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -69251,9 +67435,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -69261,10 +67445,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -69294,7 +67478,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -69302,7 +67486,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -69310,7 +67494,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -69320,7 +67504,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -69414,54 +67598,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -70220,30 +68404,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lasx } // namespace simdjson @@ -70316,6 +68476,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -70329,6 +68493,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -70507,6 +68683,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -70526,7 +68716,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -70567,7 +68757,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -70585,20 +68781,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::lasx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::lasx::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::lasx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::lasx::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -70614,7 +68806,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -70640,7 +68832,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -70773,6 +68965,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -70956,6 +69193,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -70994,6 +69233,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -71005,17 +69269,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -71115,25 +69373,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -71171,6 +69434,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -71215,6 +69483,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -71349,10 +69620,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -71415,9 +69683,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -71439,16 +69707,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = lasx::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { lasx::builder::string_builder b(initial_capacity); lasx::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for lasx */ /* including simdjson/generic/builder/fractured_json_builder.h for lasx: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for lasx */ @@ -71587,6 +69855,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -71699,6 +69968,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -71714,82 +69989,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -71962,6 +70162,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -71989,10 +70190,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -72273,10 +70470,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -72299,22 +70507,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -72326,7 +70575,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -72334,7 +70583,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -72344,124 +70593,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -72489,20 +70628,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -72512,9 +70653,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -72528,11 +70669,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -72548,16 +70689,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -72565,9 +70706,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -72581,7 +70722,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -72594,9 +70735,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -72604,10 +70745,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -72637,7 +70778,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -72645,7 +70786,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -72653,7 +70794,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -72663,7 +70804,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -72757,54 +70898,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -73515,23 +71656,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin return { (v == '\\').to_bitmask(), (v == '"').to_bitmask() }; } -struct escaping { - static constexpr uint64_t BYTES_PROCESSED = sizeof(simd8); - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - return { ((v == '"') | (v == '\\') | (v == 32)).to_bitmask() }; -} - } // unnamed namespace } // namespace rvv_vls @@ -73662,6 +71786,10 @@ namespace builder { class string_builder { public: simdjson_inline string_builder(size_t initial_capacity = DEFAULT_INITIAL_CAPACITY); + string_builder(const string_builder &) = delete; + string_builder &operator=(const string_builder &) = delete; + string_builder(string_builder &&) noexcept = default; + string_builder &operator=(string_builder &&) noexcept = default; static constexpr size_t DEFAULT_INITIAL_CAPACITY = 1024; @@ -73675,6 +71803,18 @@ class string_builder { typename = typename std::enable_if::value>::type> simdjson_inline void append(number_type v) noexcept; +#ifdef __SIZEOF_INT128__ + /** + * Integers wider than 64 bits are not supported. Without these overloads, a + * 128-bit integer would either be truncated to 64 bits by append(number_type) + * (when the standard library counts it as arithmetic, as libc++ does) or be + * converted to char by append(char) (when it does not, as libstdc++ does in + * strict ISO mode). + */ + void append(__int128_t v) noexcept = delete; + void append(__uint128_t v) noexcept = delete; +#endif + /** * Append character c. */ @@ -73853,6 +71993,20 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline simdjson_result c_str() noexcept; + /** + * Moves the written JSON buffer into out, replacing its content, without + * copying it. When the content fills at most half of the buffer, it is + * copied instead, so that a small result does not keep the whole buffer + * allocated. The builder is left empty: it keeps its buffer when the + * content was copied, and gives up its memory when the buffer was moved. + * Returns an error, and leaves out and the builder unchanged, if memory + * allocation failed. + * + * The result may not be valid UTF-8 if some of your content was not valid UTF-8. + * Use validate_unicode() to check the content. + */ + simdjson_warn_unused inline error_code move_to(std::string &out) noexcept; + /** * Return true if the content is valid UTF-8. */ @@ -73872,7 +72026,7 @@ requires (!std::is_convertible::value && !concepts::optiona // forces those reloads when accessed via members of *this). User // code should NOT call these directly. // ============================================================ - simdjson_inline char *unsafe_data() noexcept { return buffer.get(); } + simdjson_inline char *unsafe_data() noexcept { return buffer_data(); } simdjson_inline size_t unsafe_position() const noexcept { return position; } simdjson_inline size_t unsafe_capacity() const noexcept { return capacity; } simdjson_inline void unsafe_set_position(size_t p) noexcept { position = p; } @@ -73913,7 +72067,13 @@ requires (!std::is_convertible::value && !concepts::optiona */ simdjson_inline void set_valid(bool valid) noexcept; - std::unique_ptr buffer{}; + simdjson_inline char *buffer_data() noexcept { return &buffer[0]; } + simdjson_inline const char *buffer_data() const noexcept { return buffer.data(); } + + // We own capacity bytes: buffer.size() == capacity. Only the first position + // bytes hold JSON. A std::string lets move_to() hand the result over + // without a copy. + std::string buffer{}; size_t position{0}; size_t capacity{0}; bool is_valid{true}; @@ -73931,20 +72091,16 @@ template simdjson_warn_unused simdjson_result to_json(const Z &z, size_t initial_capacity = simdjson::rvv_vls::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::rvv_vls::builder::string_builder b(initial_capacity); b.append(z); - std::string_view s; - auto e = b.view().get(s); + std::string s; + auto e = b.move_to(s); if(e) { return e; } - return std::string(s); + return s; } template simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initial_capacity = simdjson::rvv_vls::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { simdjson::rvv_vls::builder::string_builder b(initial_capacity); b.append(z); - std::string_view sv; - auto e = b.view().get(sv); - if(e) { return e; } - s.assign(sv.data(), sv.size()); - return simdjson::SUCCESS; + return b.move_to(s); } #endif @@ -73960,7 +72116,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi #ifndef SIMDJSON_GENERIC_BUILDER_H /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ -/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_H */ +/* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_BUILDER_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ /* amalgamation skipped (editor-only): #include "simdjson/concepts.h" */ /* amalgamation skipped (editor-only): #include "simdjson/annotations.h" */ @@ -73986,7 +72142,7 @@ namespace builder { // included at the point this header is parsed; without these forwards, // name lookup falls back to the wrong outer namespace). namespace internal { -simdjson_really_inline char *write_uint_jeaiii(char *p, uint64_t v) noexcept; +using simdjson::internal::write_uint_jeaiii; simdjson_inline char *write_double(char *p, double v) noexcept; } // namespace internal simdjson_really_inline size_t write_string_escaped(const std::string_view input, char *out); @@ -74119,6 +72275,51 @@ consteval bool is_serialized_member(std::meta::info dm) { && !simdjson::detail::has_annotation(dm, ^^simdjson::detail::skip_serializing_tag); } +template +consteval bool members_reach(); + +// Whether a value of type T may hold a value of type Target, directly or +// through optionals, smart pointers, containers and members. Visiting lists +// the structures being searched, so that the search terminates. +template +consteval bool reaches() { + if constexpr (std::is_same_v) { + return true; + } else if constexpr ((std::is_same_v || ...) || require_custom_serialization || + std::is_same_v || std::is_same_v) { + return false; + } else if constexpr (concepts::optional_type || concepts::smart_pointer) { + return reaches())>, Visiting...>(); + } else if constexpr (concepts::string_view_keyed_map) { + return reaches, Visiting...>(); + } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { + return reaches>, Visiting...>(); + } else if constexpr (std::is_class_v) { + return members_reach(); + } else { + return false; + } +} + +template +consteval bool members_reach() { + bool found = false; + template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { + if constexpr (is_serialized_member(dm)) { + found = found || reaches().[:dm:])>, T, Visiting...>(); + } + }; + return found; +} + +// Whether the structure T may contain itself, as in +// struct Rule { std::unique_ptr subrule; }. Serializing such a type +// recurses at runtime, which simdjson_really_inline functions cannot do. +template +consteval bool is_recursive() { + return members_reach(); +} + // False when the member has a skip_serializing_if annotation and // pred(value) is true. template @@ -74302,6 +72503,8 @@ simdjson_really_inline constexpr void atom(W &w, const T &m) { template::value && !std::is_same_v>::type> simdjson_really_inline constexpr void atom(W &w, const number_type t) { + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // Booleans / floats: defer to string_builder (rare path; keeps writer hot // path free of float-formatter machinery). For integers, write directly // via jeaiii using local pos. @@ -74340,6 +72543,31 @@ simdjson_really_inline constexpr void atom(W &w, const number_type t) { } } +namespace annotation_detail { + +template +simdjson_really_inline void atom_object(W &w, const T &t) { + if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { + // A transparent structure is serialized as its single member. + constexpr auto dm = simdjson::detail::transparent_member(^^T); + atom_member(w, t.[:dm:]); + } else { + bool first = true; + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '{'; + atom_fields(w, t, first); + if (!w.ensure(1)) { return; } + w.ptr[w.pos++] = '}'; + } +} + +template +void atom_object_outlined(W &w, const T &t) { + atom_object(w, t); +} + +} // namespace annotation_detail + template requires(std::is_class_v && !concepts::container_but_not_string && !concepts::string_view_keyed_map && @@ -74351,17 +72579,11 @@ template !std::is_same_v && !std::is_same_v && !require_custom_serialization) simdjson_really_inline constexpr void atom(W &w, const T &t) { - if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { - // A transparent structure is serialized as its single member. - constexpr auto dm = simdjson::detail::transparent_member(^^T); - annotation_detail::atom_member(w, t.[:dm:]); + if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + annotation_detail::atom_object_outlined(w, t); } else { - bool first = true; - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '{'; - annotation_detail::atom_fields(w, t, first); - if (!w.ensure(1)) { return; } - w.ptr[w.pos++] = '}'; + annotation_detail::atom_object(w, t); } } @@ -74461,25 +72683,30 @@ namespace bound_detail { // Whether size_bound covers everything that atom() writes for T: not when a // member is serialized by a with serializer, which writes an unknown // amount through the string_builder. -template +// Visiting lists the structures being checked, so that a recursive type +// (struct Node { std::unique_ptr next; }) does not recurse forever: a +// type already being visited adds nothing, its members get checked once. +template consteval bool is_bounded() { - if constexpr (require_custom_serialization) { + if constexpr ((std::is_same_v || ...)) { + return true; + } else if constexpr (require_custom_serialization) { return false; } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_arithmetic_v || std::is_enum_v) { return true; } else if constexpr (concepts::optional_type || concepts::smart_pointer) { - return is_bounded())>>(); + return is_bounded())>, Visiting...>(); } else if constexpr (concepts::string_view_keyed_map) { - return is_bounded>(); + return is_bounded, Visiting...>(); } else if constexpr (concepts::container_but_not_string || concepts::appendable_containers) { - return is_bounded>>(); + return is_bounded>, Visiting...>(); } else { bool bounded = true; template for (constexpr auto dm : std::define_static_array(std::meta::nonstatic_data_members_of(^^T, std::meta::access_context::unchecked()))) { if constexpr (annotation_detail::is_serialized_member(dm)) { bounded = bounded && simdjson::detail::annotation_of_template(dm, ^^simdjson::detail::with_t) == std::meta::info{} && - is_bounded().[:dm:])>>(); + is_bounded().[:dm:])>, T, Visiting...>(); } }; return bounded; @@ -74517,6 +72744,11 @@ simdjson_really_inline size_t fields_bound(const T &t) noexcept { return bound; } +template +size_t fields_bound_outlined(const T &t) noexcept { + return fields_bound(t); +} + template simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { if constexpr (std::is_same_v) { @@ -74561,6 +72793,9 @@ simdjson_really_inline size_t size_bound([[maybe_unused]] const T &t) noexcept { } else if constexpr (simdjson::detail::has_annotation(^^T, ^^simdjson::detail::transparent_tag)) { constexpr auto dm = simdjson::detail::transparent_member(^^T); return size_bound(t.[:dm:]); + } else if constexpr (annotation_detail::is_recursive()) { + // Break the cycle of simdjson_really_inline calls. + return 2 + fields_bound_outlined(t); } else { return 2 + fields_bound(t); } @@ -74695,10 +72930,7 @@ simdjson_warn_unused error_code to_json(const Z &z, std::string &s, size_t initi } else { string_builder b(initial_capacity); append(b, z); - std::string_view view; - if(auto e = b.view().get(view); e) { return e; } - s.assign(view); - return SUCCESS; + return b.move_to(s); } } @@ -74761,9 +72993,9 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = string_builder::DEFAULT_INITIAL_CAPACITY) { string_builder b(initial_capacity); extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } SIMDJSON_POP_DISABLE_WARNINGS @@ -74785,16 +73017,16 @@ template simdjson_warn_unused simdjson_result extract_from(const T &obj, size_t initial_capacity = rvv_vls::builder::string_builder::DEFAULT_INITIAL_CAPACITY) { rvv_vls::builder::string_builder b(initial_capacity); rvv_vls::builder::extract_from(b, obj); - std::string_view s; - if(auto e = b.view().get(s); e) { return e; } - return std::string(s); + std::string s; + if(auto e = b.move_to(s); e) { return e; } + return s; } } // namespace simdjson #endif // SIMDJSON_STATIC_REFLECTION -#endif +#endif // SIMDJSON_GENERIC_BUILDER_H /* end file simdjson/generic/builder/json_builder.h for rvv_vls */ /* including simdjson/generic/builder/fractured_json_builder.h for rvv_vls: #include "simdjson/generic/builder/fractured_json_builder.h" */ /* begin file simdjson/generic/builder/fractured_json_builder.h for rvv_vls */ @@ -74933,6 +73165,7 @@ simdjson_warn_unused simdjson_result extract_fractured_json( /* amalgamation skipped (editor-only): #ifndef SIMDJSON_CONDITIONAL_INCLUDE */ /* amalgamation skipped (editor-only): #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H */ /* amalgamation skipped (editor-only): #include "simdjson/generic/builder/json_string_builder.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/internal/jeaiii.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ /* @@ -75045,6 +73278,12 @@ inline bool has_json_escapable_byte(uint64_t x) { **/ +#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON +#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 +#endif + +// With block escaping (SSE2/NEON), write_string_escaped scans the input itself. +#if !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, @@ -75060,82 +73299,7 @@ find_next_json_quotable_character_scalar(const std::string_view view, // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. -#if SIMDJSON_EXPERIMENTAL_HAS_NEON -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants for characters requiring escape - uint8x16_t v34 = vdupq_n_u8(34); // '"' - uint8x16_t v92 = vdupq_n_u8(92); // '\\' - uint8x16_t v32 = vdupq_n_u8(32); // control char threshold - - while (remaining >= 16) { - uint8x16_t word = vld1q_u8(ptr); - - // Check for quotable characters: '"', '\\', or control chars (< 32) - uint8x16_t needs_escape = vceqq_u8(word, v34); - needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); - needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); - - const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); - const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); - if(mask != 0) { - size_t offset = ptr - reinterpret_cast(view.data()); - auto trailing_zero = trailing_zeroes(mask); - return offset + (trailing_zero >> 2); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 -simdjson_inline size_t -find_next_json_quotable_character(const std::string_view view, - size_t location) noexcept { - const size_t len = view.size(); - const uint8_t *ptr = - reinterpret_cast(view.data()) + location; - size_t remaining = len - location; - - // SIMD constants - __m128i v34 = _mm_set1_epi8(34); // '"' - __m128i v92 = _mm_set1_epi8(92); // '\\' - __m128i v31 = _mm_set1_epi8(31); // for control char detection - - while (remaining >= 16) { - __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); - - // Check for quotable characters - __m128i needs_escape = _mm_cmpeq_epi8(word, v34); - needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); - needs_escape = _mm_or_si128( - needs_escape, - _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); - - int mask = _mm_movemask_epi8(needs_escape); - if (mask != 0) { - // Found quotable character - use trailing zero count to find position - size_t offset = ptr - reinterpret_cast(view.data()); - return offset + trailing_zeroes(mask); - } - ptr += 16; - remaining -= 16; - } - - // Scalar fallback for remaining bytes - size_t current = len - remaining; - return find_next_json_quotable_character_scalar(view, current); -} -#elif SIMDJSON_EXPERIMENTAL_HAS_LASX +#if SIMDJSON_EXPERIMENTAL_HAS_LASX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { @@ -75308,6 +73472,7 @@ find_next_json_quotable_character(const std::string_view view, return find_next_json_quotable_character_scalar(view, location); } #endif +#endif // !SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", @@ -75335,10 +73500,6 @@ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&o } } -#if SIMDJSON_EXPERIMENTAL_HAS_SSE2 || SIMDJSON_EXPERIMENTAL_HAS_NEON -#define SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE 1 -#endif - #if SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 @@ -75619,10 +73780,21 @@ inline size_t write_string_escaped(const std::string_view input, char *out) { #undef SIMDJSON_BUILDER_HAS_BLOCK_ESCAPE -simdjson_inline string_builder::string_builder(size_t initial_capacity) - : buffer(new(std::nothrow) char[initial_capacity]), position(0), - capacity(buffer.get() != nullptr ? initial_capacity : 0), - is_valid(buffer.get() != nullptr) {} +namespace internal { +// Sets the size of s to n without initializing the new bytes when the +// standard library allows it (C++23 resize_and_overwrite). +simdjson_inline void resize_uninitialized(std::string &s, size_t n) { +#if defined(__cpp_lib_string_resize_and_overwrite) && __cpp_lib_string_resize_and_overwrite >= 202110L + s.resize_and_overwrite(n, [](char *, size_t len) noexcept { return len; }); +#else + s.resize(n); +#endif +} +} // namespace internal + +simdjson_inline string_builder::string_builder(size_t initial_capacity) { + grow_buffer(initial_capacity); +} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and @@ -75645,22 +73817,63 @@ inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } - std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); - if (new_buffer.get() == nullptr) { + std::string new_buffer; + if (desired_capacity > new_buffer.max_size()) { + set_valid(false); + return; + } +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + internal::resize_uninitialized(new_buffer, desired_capacity); + } catch (...) { // std::bad_alloc set_valid(false); return; } - std::memcpy(new_buffer.get(), buffer.get(), position); +#else + // Without exceptions, a failed allocation terminates the program. + internal::resize_uninitialized(new_buffer, desired_capacity); +#endif + std::memcpy(&new_buffer[0], buffer_data(), position); buffer.swap(new_buffer); capacity = desired_capacity; } +// Not simdjson_inline: MSVC cannot force-inline a function with a try block (C4714). +inline error_code string_builder::move_to(std::string &out) noexcept { + if (!is_valid) { + return simdjson::OUT_OF_CAPACITY; + } + if (position <= buffer.capacity() / 2) { + // Most of the buffer is unused: copy the bytes out, so that a small + // result does not keep the whole buffer allocated. The builder keeps its + // buffer for what it writes next. +#if defined(__cpp_exceptions) || defined(_CPPUNWIND) + try { + out.assign(buffer.data(), position); + } catch (...) { // std::bad_alloc + return simdjson::MEMALLOC; + } +#else + // Without exceptions, a failed allocation terminates the program. + out.assign(buffer.data(), position); +#endif + position = 0; + return simdjson::SUCCESS; + } + buffer.resize(position); // shrinking never allocates + out = std::move(buffer); + std::string().swap(buffer); + position = 0; + capacity = 0; + return simdjson::SUCCESS; +} + simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; - buffer.reset(); + std::string().swap(buffer); } else { is_valid = true; } @@ -75672,7 +73885,7 @@ simdjson_inline size_t string_builder::size() const noexcept { simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { - buffer.get()[position++] = c; + buffer_data()[position++] = c; } } @@ -75680,7 +73893,7 @@ simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { - std::memcpy(buffer.get() + position, null_literal, null_len); + std::memcpy(buffer_data() + position, null_literal, null_len); position += null_len; } } @@ -75690,124 +73903,14 @@ simdjson_inline void string_builder::clear() noexcept { // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; - buffer.reset(); + std::string().swap(buffer); is_valid = true; } } namespace internal { -// Integer to decimal: James Edward Anhalt III's algorithm -static const char jeaiii_dd[201] = - "00010203040506070809101112131415161718192021222324252627282930313233343536373839" - "40414243444546474849505152535455565758596061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; -static const char jeaiii_fd[201] = - "0\0" "1\0" "2\0" "3\0" "4\0" "5\0" "6\0" "7\0" "8\0" "9\0" - "10111213141516171819202122232425262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374757677787980818283848586878889" - "90919293949596979899"; - -simdjson_really_inline void jeaiii_write_dd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_dd[2 * k], 2); -} -simdjson_really_inline void jeaiii_write_fd(char *p, uint64_t k) noexcept { - std::memcpy(p, &jeaiii_fd[2 * k], 2); -} - -// Caller guarantees n < 10^8. Writes 1 to 8 digits. -simdjson_really_inline char *jeaiii_lt1e8(char *b, uint32_t n) noexcept { - constexpr uint64_t mask24 = (uint64_t(1) << 24) - 1; - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - if (n < 100) { - jeaiii_write_fd(b, n); - return n < 10 ? b + 1 : b + 2; - } - if (n < 1000000) { - if (n < 10000) { - const uint32_t f0 = uint32_t(10 * (1 << 24) / 1e3 + 1) * n; - jeaiii_write_fd(b, f0 >> 24); - b -= n < 1000; - const uint32_t f2 = uint32_t(f0 & mask24) * 100; - jeaiii_write_dd(b + 2, f2 >> 24); - return b + 4; - } - const uint64_t f0 = uint64_t(10 * (1ull << 32) / 1e5 + 1) * n; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 100000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - return b + 6; - } - const uint64_t f0 = uint64_t(10 * (1ull << 48) / 1e7 + 1) * n >> 16; - jeaiii_write_fd(b, f0 >> 32); - b -= n < 10000000; - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees z < 10^8. Always writes exactly 8 digits. -simdjson_really_inline char *jeaiii_8_digits(char *b, uint32_t z) noexcept { - constexpr uint64_t mask32 = (uint64_t(1) << 32) - 1; - const uint64_t f0 = (uint64_t((1ull << 48) / 1e6 + 1) * z >> 16) + 1; - jeaiii_write_dd(b, f0 >> 32); - const uint64_t f2 = (f0 & mask32) * 100; - jeaiii_write_dd(b + 2, f2 >> 32); - const uint64_t f4 = (f2 & mask32) * 100; - jeaiii_write_dd(b + 4, f4 >> 32); - const uint64_t f6 = (f4 & mask32) * 100; - jeaiii_write_dd(b + 6, f6 >> 32); - return b + 8; -} - -// Caller guarantees 10^8 <= n < 2^32. Writes 9 or 10 digits. -simdjson_really_inline char *jeaiii_9_or_10(char *b, uint64_t n) noexcept { - constexpr uint64_t mask57 = (uint64_t(1) << 57) - 1; - const uint64_t f0 = uint64_t(10 * (1ull << 57) / 1e9 + 1) * n; - jeaiii_write_fd(b, f0 >> 57); - b -= n < 1000000000; - const uint64_t f2 = (f0 & mask57) * 100; - jeaiii_write_dd(b + 2, f2 >> 57); - const uint64_t f4 = (f2 & mask57) * 100; - jeaiii_write_dd(b + 4, f4 >> 57); - const uint64_t f6 = (f4 & mask57) * 100; - jeaiii_write_dd(b + 6, f6 >> 57); - const uint64_t f8 = (f6 & mask57) * 100; - jeaiii_write_dd(b + 8, f8 >> 57); - return b + 10; -} - -simdjson_really_inline char *write_uint_jeaiii(char *b, uint64_t n) noexcept { - if (n < 100000000) { - return jeaiii_lt1e8(b, uint32_t(n)); - } - if (n < (uint64_t(1) << 32)) { - return jeaiii_9_or_10(b, n); - } - // At least 10 digits: the low 8 digits, and 2 to 12 digits above them. - const uint32_t z = uint32_t(n % 100000000); - uint64_t u = n / 100000000; - if (u < 100000000) { - // u has 2 to 8 digits (if u < 10, n would be below 2^32). - b = jeaiii_lt1e8(b, uint32_t(u)); - } else if (u < (uint64_t(1) << 32)) { - b = jeaiii_9_or_10(b, u); - } else { - // u has 11 or 12 digits: split off 8 more. - const uint32_t y = uint32_t(u % 100000000); - u /= 100000000; - b = jeaiii_lt1e8(b, uint32_t(u)); // 3 or 4 digits - b = jeaiii_8_digits(b, y); - } - return jeaiii_8_digits(b, z); -} +using simdjson::internal::write_uint_jeaiii; // Writes v at p, which must have to_chars_buffer_size bytes available, and // returns the end of what was written. @@ -75835,20 +73938,22 @@ simdjson_inline void string_builder::append(number_type v) noexcept { std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); + static_assert(sizeof(number_type) <= sizeof(uint64_t), + "Integers wider than 64 bits are not supported"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { - std::memcpy(buffer.get() + position, true_literal, true_len); + std::memcpy(buffer_data() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { - std::memcpy(buffer.get() + position, false_literal, false_len); + std::memcpy(buffer_data() + position, false_literal, false_len); position += false_len; } } @@ -75858,9 +73963,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( - buffer.get() + position, + buffer_data() + position, static_cast(static_cast(v))); - position = end - buffer.get(); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { @@ -75874,11 +73979,11 @@ simdjson_inline void string_builder::append(number_type v) noexcept { ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( - buffer.get() + position, static_cast(pv)); - position = end - buffer.get(); + buffer_data() + position, static_cast(pv)); + position = end - buffer_data(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { @@ -75894,16 +73999,16 @@ simdjson_inline void string_builder::append(number_type v) noexcept { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; - std::memcpy(buffer.get() + position, nan_literal, nan_len); + std::memcpy(buffer_data() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { - buffer.get()[position] = '-'; + buffer_data()[position] = '-'; ++position; } - std::memcpy(buffer.get() + position, inf_literal, inf_len); + std::memcpy(buffer_data() + position, inf_literal, inf_len); position += inf_len; } return; @@ -75911,9 +74016,9 @@ simdjson_inline void string_builder::append(number_type v) noexcept { #endif // We could specialize for float. - char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, + char *end = simdjson::internal::to_chars(buffer_data() + position, nullptr, double(v)); - position = end - buffer.get(); + position = end - buffer_data(); } } } @@ -75927,7 +74032,7 @@ string_builder::escape_and_append(std::string_view input) noexcept { return; } if (capacity_check(6 * input.size())) { - position += write_string_escaped(input, buffer.get() + position); + position += write_string_escaped(input, buffer_data() + position); } } @@ -75940,9 +74045,9 @@ string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { return; } if (capacity_check(2 + 6 * input.size())) { - buffer.get()[position++] = '"'; - position += write_string_escaped(input, buffer.get() + position); - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; + position += write_string_escaped(input, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -75950,10 +74055,10 @@ simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { - buffer.get()[position++] = '"'; + buffer_data()[position++] = '"'; std::string_view cinput(&input, 1); - position += write_string_escaped(cinput, buffer.get() + position); - buffer.get()[position++] = '"'; + position += write_string_escaped(cinput, buffer_data() + position); + buffer_data()[position++] = '"'; } } @@ -75983,7 +74088,7 @@ simdjson_inline void string_builder::append_raw(const char *c) noexcept { simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { - std::memcpy(buffer.get() + position, input.data(), input.size()); + std::memcpy(buffer_data() + position, input.data(), input.size()); position += input.size(); } } @@ -75991,7 +74096,7 @@ string_builder::append_raw(std::string_view input) noexcept { simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { - std::memcpy(buffer.get() + position, str, len); + std::memcpy(buffer_data() + position, str, len); position += len; } } @@ -75999,7 +74104,7 @@ simdjson_inline void string_builder::append_raw(const char *str, template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { - std::memcpy(buffer.get() + position, str, N); + std::memcpy(buffer_data() + position, str, N); position += N; } } @@ -76009,7 +74114,7 @@ template simdjson_inline void string_builder::append_static_string() noexcept { constexpr auto& padded = constevalutil::static_string; if (capacity_check(padded.size())) { - std::memcpy(buffer.get() + position, padded.data(), padded.size()); + std::memcpy(buffer_data() + position, padded.data(), padded.size()); position += constevalutil::static_string_size(); } } @@ -76103,54 +74208,54 @@ string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } - return std::string_view(buffer.get(), position); + return std::string_view(buffer_data(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { - buffer.get()[position] = '\0'; - return buffer.get(); + buffer_data()[position] = '\0'; + return buffer_data(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { - return simdjson::validate_utf8(buffer.get(), position); + return simdjson::validate_utf8(buffer_data(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '{'; + buffer_data()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '}'; + buffer_data()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = '['; + buffer_data()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ']'; + buffer_data()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ','; + buffer_data()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { - buffer.get()[position++] = ':'; + buffer_data()[position++] = ':'; } } @@ -77201,31 +75306,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin }; } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - (is_backslash | is_quote | is_control).to_bitmask64() - }; -} - - } // unnamed namespace } // namespace arm64 @@ -77438,11 +75518,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -77843,16 +75918,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -77935,6 +76001,7 @@ struct simdjson_result : public arm64::implemen /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -78788,11 +76855,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -79481,6 +77556,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -79513,8 +77589,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -79869,6 +77948,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -80442,6 +78543,7 @@ struct simdjson_result : public arm64::implementation_s /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -80634,6 +78736,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -81643,6 +79752,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -81716,6 +79828,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -81739,6 +79853,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -81765,7 +79881,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -82092,8 +80210,6 @@ struct simdjson_result : public arm64::implementation using arm64::implementation_simdjson_result_base::operator*; using arm64::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator arm64::ondemand::array() & noexcept(false); simdjson_inline operator arm64::ondemand::object() & noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false); @@ -82128,6 +80244,8 @@ struct simdjson_result : public arm64::implementation simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -82254,6 +80372,8 @@ struct simdjson_result : public arm64::impl simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -84215,6 +82335,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -84759,6 +82880,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -85623,6 +83751,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -85671,6 +83807,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -86263,251 +84401,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -86622,10 +84517,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -86709,12 +84603,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -86723,11 +84628,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -86746,16 +84651,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -87413,6 +85318,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -87421,10 +85339,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -87797,7 +85721,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -88097,7 +86022,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -88110,10 +86041,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -88160,7 +86117,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -88178,7 +86137,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -88201,7 +86162,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -88539,11 +86502,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator arm64::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -88604,6 +86562,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -89090,6 +87053,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -89481,6 +87449,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -89630,7 +87605,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -89642,6 +87620,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -89658,7 +87641,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -89770,12 +87758,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -89946,11 +87936,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -89974,6 +87967,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -90029,7 +88034,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -90314,6 +88342,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -90955,7 +88994,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -91048,9 +89089,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -91063,11 +89116,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -91076,17 +89129,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -91739,6 +89792,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -91756,6 +89810,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -91811,6 +89866,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -91837,6 +89893,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -91872,6 +89929,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -91917,6 +89975,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -92071,17 +90130,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -92107,7 +90167,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -92120,7 +90180,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -92145,7 +90205,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -92257,7 +90317,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -92272,7 +90336,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -92331,6 +90395,8 @@ simdjson_inline simdjson_result::simdjson_resul /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace arm64 { namespace ondemand { @@ -92377,6 +90443,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -92786,6 +90856,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -92991,8 +91065,10 @@ simdjson_inline simdjson_result value_iterator::get_root_num // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -93013,8 +91089,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -93084,10 +91162,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -93117,10 +91195,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -93137,8 +91215,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -93157,8 +91237,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -93177,8 +91259,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -93194,8 +91278,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -93216,7 +91302,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -93856,49 +91942,6 @@ namespace json_path { using ::simdjson::arm64::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -93912,77 +91955,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -93991,7 +91963,6 @@ struct path_accessor { using value = ::simdjson::arm64::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -94106,93 +92077,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -94322,55 +92206,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -94870,23 +92705,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 1; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits; } - simdjson_inline int escape_index() { return 0; } - - bool escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - dst[0] = src[0]; - return { (src[0] == '\\') || (src[0] == '"') || (src[0] < 32) }; -} - } // unnamed namespace } // namespace fallback } // namespace simdjson @@ -95185,11 +93003,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -95590,16 +93403,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -95682,6 +93486,7 @@ struct simdjson_result : public fallback::im /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -96535,11 +94340,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -97228,6 +95041,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -97260,8 +95074,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -97616,6 +95433,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -98189,6 +96028,7 @@ struct simdjson_result : public fallback::implementa /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -98381,6 +96221,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -99390,6 +97237,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -99463,6 +97313,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -99486,6 +97338,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -99512,7 +97366,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -99839,134 +97695,134 @@ struct simdjson_result : public fallback::implemen using fallback::implementation_simdjson_result_base::operator*; using fallback::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator fallback::ondemand::array() & noexcept(false); - simdjson_inline operator fallback::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator fallback::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator fallback::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public fallback::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(fallback::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator fallback::ondemand::array() & noexcept(false); + simdjson_inline operator fallback::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator fallback::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator fallback::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public fallback::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(fallback::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator fallback::ondemand::array() & noexcept(false); simdjson_inline operator fallback::ondemand::object() & noexcept(false); @@ -100001,6 +97857,8 @@ struct simdjson_result : public fallback simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -101962,6 +99820,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -102506,6 +100365,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -103370,6 +101236,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -103418,6 +101292,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -104010,251 +101886,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -104369,10 +102002,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -104456,12 +102088,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -104470,11 +102113,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -104493,16 +102136,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -105160,6 +102803,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -105168,10 +102824,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -105544,7 +103206,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -105844,7 +103507,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -105857,10 +103526,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -105907,7 +103602,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -105925,7 +103622,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -105948,7 +103647,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -106286,11 +103987,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator fallback::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -106351,6 +104047,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -106837,6 +104538,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -107228,6 +104934,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -107377,7 +105090,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -107389,6 +105105,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -107405,7 +105126,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -107517,12 +105243,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -107693,11 +105421,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -107721,6 +105452,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -107776,7 +105519,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -108061,6 +105827,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -108702,7 +106479,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -108795,9 +106574,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -108810,11 +106601,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -108823,17 +106614,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -109486,6 +107277,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -109503,6 +107295,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -109558,6 +107351,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -109584,6 +107378,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -109619,6 +107414,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -109664,6 +107460,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -109818,17 +107615,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -109854,7 +107652,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -109867,7 +107665,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -109892,7 +107690,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -110004,7 +107802,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -110019,7 +107821,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -110078,6 +107880,8 @@ simdjson_inline simdjson_result::simdjson_re /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace fallback { namespace ondemand { @@ -110124,6 +107928,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -110533,6 +108341,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -110738,8 +108550,10 @@ simdjson_inline simdjson_result value_iterator::get_root_ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -110760,8 +108574,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -110831,10 +108647,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -110864,10 +108680,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -110884,8 +108700,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -110904,8 +108722,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -110924,8 +108744,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -110941,8 +108763,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -110963,7 +108787,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -111603,49 +109427,6 @@ namespace json_path { using ::simdjson::fallback::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -111659,77 +109440,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -111738,7 +109448,6 @@ struct path_accessor { using value = ::simdjson::fallback::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -111853,93 +109562,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -112069,55 +109691,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -113176,30 +110749,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 32; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace haswell } // namespace simdjson @@ -113409,11 +110958,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -113814,16 +111358,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -113906,6 +111441,7 @@ struct simdjson_result : public haswell::impl /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -114759,11 +112295,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -115452,6 +112996,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -115484,8 +113029,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -115840,6 +113388,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -116413,6 +113983,7 @@ struct simdjson_result : public haswell::implementati /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -116605,6 +114176,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -117614,6 +115192,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -117687,6 +115268,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -117710,6 +115293,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -117736,7 +115321,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -118063,134 +115650,134 @@ struct simdjson_result : public haswell::implementa using haswell::implementation_simdjson_result_base::operator*; using haswell::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator haswell::ondemand::array() & noexcept(false); - simdjson_inline operator haswell::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator haswell::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator haswell::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public haswell::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(haswell::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator haswell::ondemand::array() & noexcept(false); + simdjson_inline operator haswell::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator haswell::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator haswell::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public haswell::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(haswell::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator haswell::ondemand::array() & noexcept(false); simdjson_inline operator haswell::ondemand::object() & noexcept(false); @@ -118225,6 +115812,8 @@ struct simdjson_result : public haswell:: simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -120186,6 +117775,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -120730,6 +118320,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -121594,6 +119191,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -121642,6 +119247,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -122234,251 +119841,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -122593,10 +119957,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -122680,12 +120043,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -122694,11 +120068,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -122717,16 +120091,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -123384,6 +120758,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -123392,10 +120779,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -123768,7 +121161,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -124068,7 +121462,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -124081,10 +121481,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -124131,7 +121557,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -124149,7 +121577,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -124172,7 +121602,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -124510,11 +121942,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator haswell::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -124575,6 +122002,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -125061,6 +122493,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -125452,6 +122889,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -125601,7 +123045,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -125613,6 +123060,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -125629,7 +123081,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -125741,12 +123198,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -125917,11 +123376,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -125945,6 +123407,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -126000,7 +123474,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -126285,6 +123782,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -126926,7 +124434,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -127019,9 +124529,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -127034,11 +124556,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -127047,17 +124569,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -127710,6 +125232,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -127727,6 +125250,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -127782,6 +125306,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -127808,6 +125333,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -127843,6 +125369,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -127888,6 +125415,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -128042,17 +125570,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -128078,7 +125607,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -128091,7 +125620,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -128116,7 +125645,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -128228,7 +125757,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -128243,7 +125776,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -128302,6 +125835,8 @@ simdjson_inline simdjson_result::simdjson_res /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace haswell { namespace ondemand { @@ -128348,6 +125883,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -128757,6 +126296,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -128962,8 +126505,10 @@ simdjson_inline simdjson_result value_iterator::get_root_n // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -128984,8 +126529,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -129055,10 +126602,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -129088,10 +126635,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -129108,8 +126655,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -129128,8 +126677,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -129148,8 +126699,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -129165,8 +126718,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -129187,7 +126742,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -129827,49 +127382,6 @@ namespace json_path { using ::simdjson::haswell::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -129883,77 +127395,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -129962,7 +127403,6 @@ struct path_accessor { using value = ::simdjson::haswell::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -130077,93 +127517,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -130293,55 +127646,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -131336,34 +128640,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } - -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 64; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(uint64_t(escape_bits)); } - - __mmask64 escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - __mmask64 is_quote = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('"')); - __mmask64 is_backslash = _mm512_cmpeq_epi8_mask(v, _mm512_set1_epi8('\\')); - __mmask64 is_control = _mm512_cmplt_epi8_mask(v, _mm512_set1_epi8(32)); - return { - (is_backslash | is_quote | is_control) - }; -} - - - - } // unnamed namespace } // namespace icelake } // namespace simdjson @@ -131633,11 +128909,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -132038,16 +129309,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -132130,6 +129392,7 @@ struct simdjson_result : public icelake::impl /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -132983,11 +130246,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -133676,6 +130947,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -133708,8 +130980,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -134064,6 +131339,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -134637,6 +131934,7 @@ struct simdjson_result : public icelake::implementati /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -134829,6 +132127,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -135838,6 +133143,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -135911,6 +133219,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -135934,6 +133244,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -135960,7 +133272,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -136287,134 +133601,134 @@ struct simdjson_result : public icelake::implementa using icelake::implementation_simdjson_result_base::operator*; using icelake::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator icelake::ondemand::array() & noexcept(false); - simdjson_inline operator icelake::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator icelake::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator icelake::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public icelake::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(icelake::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator icelake::ondemand::array() & noexcept(false); + simdjson_inline operator icelake::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator icelake::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator icelake::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public icelake::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(icelake::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator icelake::ondemand::array() & noexcept(false); simdjson_inline operator icelake::ondemand::object() & noexcept(false); @@ -136449,6 +133763,8 @@ struct simdjson_result : public icelake:: simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -138410,6 +135726,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -138954,6 +136271,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -139818,6 +137142,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -139866,6 +137198,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -140458,251 +137792,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -140817,10 +137908,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -140904,12 +137994,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -140918,11 +138019,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -140941,16 +138042,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -141608,6 +138709,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -141616,10 +138730,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -141992,7 +139112,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -142292,7 +139413,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -142305,10 +139432,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -142355,7 +139508,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -142373,7 +139528,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -142396,7 +139553,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -142734,11 +139893,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator icelake::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -142799,6 +139953,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -143285,6 +140444,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -143676,6 +140840,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -143825,7 +140996,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -143837,6 +141011,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -143853,7 +141032,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -143965,12 +141149,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -144141,11 +141327,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -144169,6 +141358,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -144224,7 +141425,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -144509,6 +141733,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -145150,7 +142385,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -145243,9 +142480,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -145258,11 +142507,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -145271,17 +142520,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -145934,6 +143183,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -145951,6 +143201,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -146006,6 +143257,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -146032,6 +143284,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -146067,6 +143320,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -146112,6 +143366,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -146266,17 +143521,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -146302,7 +143558,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -146315,7 +143571,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -146340,7 +143596,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -146452,7 +143708,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -146467,7 +143727,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -146526,6 +143786,8 @@ simdjson_inline simdjson_result::simdjson_res /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace icelake { namespace ondemand { @@ -146572,6 +143834,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -146981,6 +144247,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -147186,8 +144456,10 @@ simdjson_inline simdjson_result value_iterator::get_root_n // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -147208,8 +144480,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -147279,10 +144553,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -147312,10 +144586,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -147332,8 +144606,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -147352,8 +144628,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -147372,8 +144650,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -147389,8 +144669,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -147411,7 +144693,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -148051,49 +145333,6 @@ namespace json_path { using ::simdjson::icelake::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -148107,77 +145346,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -148186,7 +145354,6 @@ struct path_accessor { using value = ::simdjson::icelake::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -148301,93 +145468,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -148517,55 +145597,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -149736,31 +146767,6 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - // We store it as a 64-bit bitmask even though we only need 16 bits. - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace ppc64 } // namespace simdjson @@ -149972,11 +146978,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -150377,16 +147378,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -150469,6 +147461,7 @@ struct simdjson_result : public ppc64::implemen /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -151322,11 +148315,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -152015,6 +149016,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -152047,8 +149049,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -152403,6 +149408,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -152976,6 +150003,7 @@ struct simdjson_result : public ppc64::implementation_s /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -153168,6 +150196,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -154177,6 +151212,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -154250,6 +151288,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -154273,6 +151313,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -154299,7 +151341,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -154626,134 +151670,134 @@ struct simdjson_result : public ppc64::implementation using ppc64::implementation_simdjson_result_base::operator*; using ppc64::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator ppc64::ondemand::array() & noexcept(false); - simdjson_inline operator ppc64::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator ppc64::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator ppc64::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public ppc64::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(ppc64::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator ppc64::ondemand::array() & noexcept(false); + simdjson_inline operator ppc64::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator ppc64::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator ppc64::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public ppc64::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(ppc64::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator ppc64::ondemand::array() & noexcept(false); simdjson_inline operator ppc64::ondemand::object() & noexcept(false); @@ -154788,6 +151832,8 @@ struct simdjson_result : public ppc64::impl simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -156749,6 +153795,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -157293,6 +154340,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -158157,6 +155211,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -158205,6 +155267,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -158797,251 +155861,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -159156,10 +155977,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -159243,12 +156063,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -159257,11 +156088,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -159280,16 +156111,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -159947,6 +156778,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -159955,10 +156799,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -160331,7 +157181,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -160631,7 +157482,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -160644,10 +157501,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -160694,7 +157577,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -160712,7 +157597,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -160735,7 +157622,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -161073,11 +157962,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator ppc64::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -161138,6 +158022,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -161624,6 +158513,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -162015,6 +158909,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -162164,7 +159065,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -162176,6 +159080,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -162192,7 +159101,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -162304,12 +159218,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -162480,11 +159396,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -162508,6 +159427,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -162563,7 +159494,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -162848,6 +159802,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -163489,7 +160454,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -163582,9 +160549,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -163597,11 +160576,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -163610,17 +160589,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -164273,6 +161252,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -164290,6 +161270,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -164345,6 +161326,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -164371,6 +161353,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -164406,6 +161389,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -164451,6 +161435,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -164605,17 +161590,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -164641,7 +161627,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -164654,7 +161640,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -164679,7 +161665,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -164791,7 +161777,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -164806,7 +161796,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -164865,6 +161855,8 @@ simdjson_inline simdjson_result::simdjson_resul /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace ppc64 { namespace ondemand { @@ -164911,6 +161903,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -165320,6 +162316,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -165525,8 +162525,10 @@ simdjson_inline simdjson_result value_iterator::get_root_num // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -165547,8 +162549,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -165618,10 +162622,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -165651,10 +162655,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -165671,8 +162675,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -165691,8 +162697,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -165711,8 +162719,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -165728,8 +162738,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -165750,7 +162762,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -166390,49 +163402,6 @@ namespace json_path { using ::simdjson::ppc64::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -166446,77 +163415,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -166525,7 +163423,6 @@ struct path_accessor { using value = ::simdjson::ppc64::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -166640,93 +163537,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -166856,55 +163666,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -168385,30 +165146,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - uint64_t((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace westmere } // namespace simdjson @@ -168618,11 +165355,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -169023,16 +165755,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -169115,6 +165838,7 @@ struct simdjson_result : public westmere::im /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -169968,11 +166692,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -170661,6 +167393,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -170693,8 +167426,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -171049,6 +167785,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -171622,6 +168380,7 @@ struct simdjson_result : public westmere::implementa /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -171814,6 +168573,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -172823,6 +169589,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -172896,6 +169665,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -172919,6 +169690,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -172945,7 +169718,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -173272,134 +170047,134 @@ struct simdjson_result : public westmere::implemen using westmere::implementation_simdjson_result_base::operator*; using westmere::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator westmere::ondemand::array() & noexcept(false); - simdjson_inline operator westmere::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator westmere::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator westmere::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public westmere::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(westmere::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator westmere::ondemand::array() & noexcept(false); + simdjson_inline operator westmere::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator westmere::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator westmere::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public westmere::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(westmere::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator westmere::ondemand::array() & noexcept(false); simdjson_inline operator westmere::ondemand::object() & noexcept(false); @@ -173434,6 +170209,8 @@ struct simdjson_result : public westmere simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -175395,6 +172172,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -175939,6 +172717,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -176803,6 +173588,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -176851,6 +173644,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -177443,251 +174238,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -177802,10 +174354,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -177889,12 +174440,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -177903,11 +174465,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -177926,16 +174488,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -178593,6 +175155,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -178601,10 +175176,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -178977,7 +175558,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -179277,7 +175859,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -179290,10 +175878,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -179340,7 +175954,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -179358,7 +175974,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -179381,7 +175999,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -179719,11 +176339,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator westmere::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -179784,6 +176399,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -180270,6 +176890,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -180661,6 +177286,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -180810,7 +177442,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -180822,6 +177457,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -180838,7 +177478,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -180950,12 +177595,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -181126,11 +177773,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -181154,6 +177804,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -181209,7 +177871,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -181494,6 +178179,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -182135,7 +178831,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -182228,9 +178926,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -182243,11 +178953,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -182256,17 +178966,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -182919,6 +179629,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -182936,6 +179647,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -182991,6 +179703,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -183017,6 +179730,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -183052,6 +179766,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -183097,6 +179812,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -183251,17 +179967,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -183287,7 +180004,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -183300,7 +180017,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -183325,7 +180042,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -183437,7 +180154,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -183452,7 +180173,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -183511,6 +180232,8 @@ simdjson_inline simdjson_result::simdjson_re /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace westmere { namespace ondemand { @@ -183557,6 +180280,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -183966,6 +180693,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -184171,8 +180902,10 @@ simdjson_inline simdjson_result value_iterator::get_root_ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -184193,8 +180926,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -184264,10 +180999,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -184297,10 +181032,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -184317,8 +181052,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -184337,8 +181074,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -184357,8 +181096,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -184374,8 +181115,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -184396,7 +181139,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -185036,49 +181779,6 @@ namespace json_path { using ::simdjson::westmere::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -185092,77 +181792,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -185171,7 +181800,6 @@ struct path_accessor { using value = ::simdjson::westmere::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -185286,93 +181914,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -185502,55 +182043,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -186519,30 +183011,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lsx } // namespace simdjson @@ -186754,11 +183222,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -187159,16 +183622,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -187251,6 +183705,7 @@ struct simdjson_result : public lsx::implementati /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -188104,11 +184559,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -188797,6 +185260,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -188829,8 +185293,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -189185,6 +185652,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -189758,6 +186247,7 @@ struct simdjson_result : public lsx::implementation_simdj /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -189950,6 +186440,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -190959,6 +187456,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -191032,6 +187532,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -191055,6 +187557,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -191081,7 +187585,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -191408,134 +187914,134 @@ struct simdjson_result : public lsx::implementation_sim using lsx::implementation_simdjson_result_base::operator*; using lsx::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator lsx::ondemand::array() & noexcept(false); - simdjson_inline operator lsx::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator lsx::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator lsx::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public lsx::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(lsx::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator lsx::ondemand::array() & noexcept(false); + simdjson_inline operator lsx::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator lsx::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator lsx::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public lsx::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(lsx::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator lsx::ondemand::array() & noexcept(false); simdjson_inline operator lsx::ondemand::object() & noexcept(false); @@ -191570,6 +188076,8 @@ struct simdjson_result : public lsx::implemen simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -193531,6 +190039,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -194075,6 +190584,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -194939,6 +191455,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -194987,6 +191511,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -195579,251 +192105,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -195938,10 +192221,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -196025,12 +192307,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -196039,11 +192332,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -196062,16 +192355,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -196729,6 +193022,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -196737,10 +193043,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -197113,7 +193425,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -197413,7 +193726,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -197426,10 +193745,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -197476,7 +193821,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -197494,7 +193841,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -197517,7 +193866,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -197855,11 +194206,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator lsx::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -197920,6 +194266,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -198406,6 +194757,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -198797,6 +195153,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -198946,7 +195309,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -198958,6 +195324,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -198974,7 +195345,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -199086,12 +195462,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -199262,11 +195640,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -199290,6 +195671,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -199345,7 +195738,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -199630,6 +196046,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -200271,7 +196698,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -200364,9 +196793,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -200379,11 +196820,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -200392,17 +196833,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -201055,6 +197496,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -201072,6 +197514,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -201127,6 +197570,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -201153,6 +197597,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -201188,6 +197633,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -201233,6 +197679,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -201387,17 +197834,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -201423,7 +197871,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -201436,7 +197884,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -201461,7 +197909,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -201573,7 +198021,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -201588,7 +198040,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -201647,6 +198099,8 @@ simdjson_inline simdjson_result::simdjson_result( /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace lsx { namespace ondemand { @@ -201693,6 +198147,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -202102,6 +198560,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -202307,8 +198769,10 @@ simdjson_inline simdjson_result value_iterator::get_root_numbe // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -202329,8 +198793,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -202400,10 +198866,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -202433,10 +198899,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -202453,8 +198919,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -202473,8 +198941,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -202493,8 +198963,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -202510,8 +198982,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -202532,7 +199006,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -203172,49 +199646,6 @@ namespace json_path { using ::simdjson::lsx::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -203228,77 +199659,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -203307,7 +199667,6 @@ struct path_accessor { using value = ::simdjson::lsx::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -203422,93 +199781,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -203638,55 +199910,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -204676,30 +200899,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin } -struct escaping { - static constexpr uint32_t BYTES_PROCESSED = 16; - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits); } - - uint64_t escape_bits; -}; // struct escaping - - - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - simd8 is_quote = (v == '"'); - simd8 is_backslash = (v == '\\'); - simd8 is_control = (v < 32); - return { - static_cast((is_backslash | is_quote | is_control).to_bitmask()) - }; -} - } // unnamed namespace } // namespace lasx } // namespace simdjson @@ -204913,11 +201112,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -205318,16 +201512,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -205410,6 +201595,7 @@ struct simdjson_result : public lasx::implementa /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -206263,11 +202449,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -206956,6 +203150,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -206988,8 +203183,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -207344,6 +203542,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -207917,6 +204137,7 @@ struct simdjson_result : public lasx::implementation_sim /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -208109,6 +204330,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -209118,6 +205346,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -209191,6 +205422,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -209214,6 +205447,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -209240,7 +205475,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -209567,134 +205804,134 @@ struct simdjson_result : public lasx::implementation_s using lasx::implementation_simdjson_result_base::operator*; using lasx::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); - simdjson_inline operator lasx::ondemand::array() & noexcept(false); - simdjson_inline operator lasx::ondemand::object() & noexcept(false); - explicit simdjson_inline operator uint64_t() noexcept(false); - explicit simdjson_inline operator int64_t() noexcept(false); - explicit simdjson_inline operator double() noexcept(false); - explicit simdjson_inline operator std::string_view() noexcept(false); - explicit simdjson_inline operator lasx::ondemand::raw_json_string() noexcept(false); - explicit simdjson_inline operator bool() noexcept(false); - simdjson_inline operator lasx::ondemand::value() noexcept(false); -#endif - simdjson_inline simdjson_result count_elements() & noexcept; - simdjson_inline simdjson_result count_fields() & noexcept; - simdjson_inline simdjson_result at(size_t index) & noexcept; - simdjson_inline simdjson_result begin() & noexcept; - simdjson_inline simdjson_result end() & noexcept; - simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field(const char *key) & noexcept; - simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; - simdjson_inline simdjson_result operator[](const char *key) & noexcept; - simdjson_result operator[](int) & noexcept = delete; - simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; - simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; - simdjson_inline simdjson_result type() noexcept; - simdjson_inline simdjson_result is_scalar() noexcept; - simdjson_inline simdjson_result is_string() noexcept; - simdjson_inline simdjson_result current_location() noexcept; - simdjson_inline int32_t current_depth() const noexcept; - simdjson_inline bool at_end() const noexcept; - simdjson_inline bool is_negative() noexcept; - simdjson_inline simdjson_result is_integer() noexcept; - simdjson_inline simdjson_result get_number_type() noexcept; - simdjson_inline simdjson_result get_number() noexcept; - /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ - simdjson_inline simdjson_result raw_json_token() noexcept; - - simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; - simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; -#if SIMDJSON_SUPPORTS_CONCEPTS - template - requires std::invocable -#else - template -#endif - simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; -#if SIMDJSON_STATIC_REFLECTION - template - requires(std::is_class_v && (sizeof...(FieldNames) > 0)) - simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; -#endif // SIMDJSON_STATIC_REFLECTION -}; - - -} // namespace simdjson - - - -namespace simdjson { - -template<> -struct simdjson_result : public lasx::implementation_simdjson_result_base { -public: - simdjson_inline simdjson_result(lasx::ondemand::document_reference value, error_code error) noexcept; - simdjson_inline simdjson_result() noexcept = default; - simdjson_inline error_code rewind() noexcept; - - simdjson_inline simdjson_result get_array() & noexcept; - simdjson_inline simdjson_result get_object() & noexcept; - simdjson_inline simdjson_result get_uint64() noexcept; - simdjson_inline simdjson_result get_uint64_in_string() noexcept; - simdjson_inline simdjson_result get_int64() noexcept; - simdjson_inline simdjson_result get_int64_in_string() noexcept; - simdjson_inline simdjson_result get_uint32() noexcept; - simdjson_inline simdjson_result get_int32() noexcept; - simdjson_inline simdjson_result get_uint16() noexcept; - simdjson_inline simdjson_result get_int16() noexcept; - simdjson_inline simdjson_result get_uint8() noexcept; - simdjson_inline simdjson_result get_int8() noexcept; - simdjson_inline simdjson_result get_double() noexcept; - simdjson_inline simdjson_result get_double_in_string() noexcept; - simdjson_inline simdjson_result get_float() noexcept; - simdjson_inline simdjson_result get_float_in_string() noexcept; -#if SIMDJSON_SUPPORTS_FLOAT32_T - simdjson_inline simdjson_result get_float32() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT32_T -#if SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_float64() noexcept; -#endif // SIMDJSON_SUPPORTS_FLOAT64_T - simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; -#if SIMDJSON_SUPPORTS_CHAR8_T - simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; -#endif // SIMDJSON_SUPPORTS_CHAR8_T - template - simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; - simdjson_inline simdjson_result get_wobbly_string() noexcept; - simdjson_inline simdjson_result get_raw_json_string() noexcept; - simdjson_inline simdjson_result get_bool() noexcept; - simdjson_inline simdjson_result get_value() noexcept; - simdjson_inline simdjson_result is_null() noexcept; - - template simdjson_inline simdjson_result get() & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline simdjson_result get() && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - - template simdjson_inline error_code get(T &out) & -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif - template simdjson_inline error_code get(T &out) && -#if SIMDJSON_SUPPORTS_CONCEPTS - noexcept(nothrow_gettable); -#else - noexcept; -#endif -#if SIMDJSON_EXCEPTIONS - template + simdjson_inline operator lasx::ondemand::array() & noexcept(false); + simdjson_inline operator lasx::ondemand::object() & noexcept(false); + explicit simdjson_inline operator uint64_t() noexcept(false); + explicit simdjson_inline operator int64_t() noexcept(false); + explicit simdjson_inline operator double() noexcept(false); + explicit simdjson_inline operator std::string_view() noexcept(false); + explicit simdjson_inline operator lasx::ondemand::raw_json_string() noexcept(false); + explicit simdjson_inline operator bool() noexcept(false); + simdjson_inline operator lasx::ondemand::value() noexcept(false); +#endif + simdjson_inline simdjson_result count_elements() & noexcept; + simdjson_inline simdjson_result count_fields() & noexcept; + simdjson_inline simdjson_result at(size_t index) & noexcept; + simdjson_inline simdjson_result begin() & noexcept; + simdjson_inline simdjson_result end() & noexcept; + simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field(const char *key) & noexcept; + simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; + simdjson_inline simdjson_result operator[](const char *key) & noexcept; + simdjson_result operator[](int) & noexcept = delete; + simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; + simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; + simdjson_inline simdjson_result type() noexcept; + simdjson_inline simdjson_result is_scalar() noexcept; + simdjson_inline simdjson_result is_string() noexcept; + simdjson_inline simdjson_result current_location() noexcept; + simdjson_inline int32_t current_depth() const noexcept; + simdjson_inline bool at_end() const noexcept; + simdjson_inline bool is_negative() noexcept; + simdjson_inline simdjson_result is_integer() noexcept; + simdjson_inline simdjson_result get_number_type() noexcept; + simdjson_inline simdjson_result get_number() noexcept; + /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ + simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; + + simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; + simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; +#if SIMDJSON_SUPPORTS_CONCEPTS + template + requires std::invocable +#else + template +#endif + simdjson_inline error_code for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept; +#if SIMDJSON_STATIC_REFLECTION + template + requires(std::is_class_v && (sizeof...(FieldNames) > 0)) + simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; +#endif // SIMDJSON_STATIC_REFLECTION +}; + + +} // namespace simdjson + + + +namespace simdjson { + +template<> +struct simdjson_result : public lasx::implementation_simdjson_result_base { +public: + simdjson_inline simdjson_result(lasx::ondemand::document_reference value, error_code error) noexcept; + simdjson_inline simdjson_result() noexcept = default; + simdjson_inline error_code rewind() noexcept; + + simdjson_inline simdjson_result get_array() & noexcept; + simdjson_inline simdjson_result get_object() & noexcept; + simdjson_inline simdjson_result get_uint64() noexcept; + simdjson_inline simdjson_result get_uint64_in_string() noexcept; + simdjson_inline simdjson_result get_int64() noexcept; + simdjson_inline simdjson_result get_int64_in_string() noexcept; + simdjson_inline simdjson_result get_uint32() noexcept; + simdjson_inline simdjson_result get_int32() noexcept; + simdjson_inline simdjson_result get_uint16() noexcept; + simdjson_inline simdjson_result get_int16() noexcept; + simdjson_inline simdjson_result get_uint8() noexcept; + simdjson_inline simdjson_result get_int8() noexcept; + simdjson_inline simdjson_result get_double() noexcept; + simdjson_inline simdjson_result get_double_in_string() noexcept; + simdjson_inline simdjson_result get_float() noexcept; + simdjson_inline simdjson_result get_float_in_string() noexcept; +#if SIMDJSON_SUPPORTS_FLOAT32_T + simdjson_inline simdjson_result get_float32() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT32_T +#if SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_float64() noexcept; +#endif // SIMDJSON_SUPPORTS_FLOAT64_T + simdjson_inline simdjson_result get_string(bool allow_replacement = false) noexcept; +#if SIMDJSON_SUPPORTS_CHAR8_T + simdjson_inline simdjson_result get_u8string(bool allow_replacement = false) noexcept; +#endif // SIMDJSON_SUPPORTS_CHAR8_T + template + simdjson_warn_unused simdjson_inline error_code get_string(string_type& receiver, bool allow_replacement = false) noexcept; + simdjson_inline simdjson_result get_wobbly_string() noexcept; + simdjson_inline simdjson_result get_raw_json_string() noexcept; + simdjson_inline simdjson_result get_bool() noexcept; + simdjson_inline simdjson_result get_value() noexcept; + simdjson_inline simdjson_result is_null() noexcept; + + template simdjson_inline simdjson_result get() & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline simdjson_result get() && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + + template simdjson_inline error_code get(T &out) & +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif + template simdjson_inline error_code get(T &out) && +#if SIMDJSON_SUPPORTS_CONCEPTS + noexcept(nothrow_gettable); +#else + noexcept; +#endif +#if SIMDJSON_EXCEPTIONS + template explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator lasx::ondemand::array() & noexcept(false); simdjson_inline operator lasx::ondemand::object() & noexcept(false); @@ -209729,6 +205966,8 @@ struct simdjson_result : public lasx::implem simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -211690,6 +207929,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -212234,6 +208474,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -213098,6 +209345,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -213146,6 +209401,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -213738,251 +209995,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -214097,10 +210111,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -214184,12 +210197,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -214198,11 +210222,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -214221,16 +210245,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -214888,6 +210912,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -214896,10 +210933,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -215272,7 +211315,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -215572,7 +211616,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -215585,10 +211635,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -215635,7 +211711,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -215653,7 +211731,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -215676,7 +211756,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -216014,11 +212096,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator lasx::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -216079,6 +212156,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -216565,6 +212647,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -216956,6 +213043,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -217105,7 +213199,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -217117,6 +213214,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -217133,7 +213235,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -217245,12 +213352,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -217421,11 +213530,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -217449,6 +213561,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -217504,7 +213628,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -217789,6 +213936,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -218430,7 +214588,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -218523,9 +214683,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -218538,11 +214710,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -218551,17 +214723,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -219214,6 +215386,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -219231,6 +215404,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -219286,6 +215460,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -219312,6 +215487,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -219347,6 +215523,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -219392,6 +215569,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -219546,17 +215724,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -219582,7 +215761,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -219595,7 +215774,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -219620,7 +215799,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -219732,7 +215911,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -219747,7 +215930,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -219806,6 +215989,8 @@ simdjson_inline simdjson_result::simdjson_result /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace lasx { namespace ondemand { @@ -219852,6 +216037,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -220261,6 +216450,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -220466,8 +216659,10 @@ simdjson_inline simdjson_result value_iterator::get_root_numb // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -220488,8 +216683,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -220559,10 +216756,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -220592,10 +216789,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -220612,8 +216809,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -220632,8 +216831,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -220652,8 +216853,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -220669,8 +216872,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -220691,7 +216896,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -221331,49 +217536,6 @@ namespace json_path { using ::simdjson::lasx::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -221387,77 +217549,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -221466,7 +217557,6 @@ struct path_accessor { using value = ::simdjson::lasx::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -221581,93 +217671,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -221797,55 +217800,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -222787,23 +218741,6 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin return { (v == '\\').to_bitmask(), (v == '"').to_bitmask() }; } -struct escaping { - static constexpr uint64_t BYTES_PROCESSED = sizeof(simd8); - simdjson_inline static escaping copy_and_find(const uint8_t *src, uint8_t *dst); - - simdjson_inline bool has_escape() { return escape_bits != 0; } - simdjson_inline int escape_index() { return trailing_zeroes(escape_bits) / 4; } - - uint64_t escape_bits; -}; // struct escaping - -simdjson_inline escaping escaping::copy_and_find(const uint8_t *src, uint8_t *dst) { - static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "escaping finder must process fewer than SIMDJSON_PADDING bytes"); - simd8 v(src); - v.store(dst); - return { ((v == '"') | (v == '\\') | (v == 32)).to_bitmask() }; -} - } // unnamed namespace } // namespace rvv_vls @@ -223075,11 +219012,6 @@ class value_iterator { public: simdjson_inline value_iterator() noexcept = default; - /** - * Denote that we're starting a document. - */ - simdjson_inline void start_document() noexcept; - /** * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. * @@ -223480,16 +219412,7 @@ class value_iterator { simdjson_warn_unused simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; simdjson_warn_unused simdjson_inline error_code end_container() noexcept; - /** - * Advance to a place expecting a value (increasing depth). - * - * @return The current token (the one left behind). - * @error TAPE_ERROR If the document ended early. - */ - simdjson_inline simdjson_result advance_to_value() noexcept; - simdjson_warn_unused simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; - simdjson_warn_unused simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; simdjson_inline bool is_at_start() const noexcept; /** @@ -223572,6 +219495,7 @@ struct simdjson_result : public rvv_vls::impl /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/deserialize.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -224425,11 +220349,19 @@ class value { */ simdjson_inline simdjson_result start_or_resume_object() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + // simdjson_inline void log_value(const char *type) const noexcept; // simdjson_inline void log_error(const char *message) const noexcept; value_iterator iter{}; + friend class array; friend class document; friend class array_iterator; friend class field; @@ -225118,6 +221050,7 @@ class json_iterator { /** * Take an input in json containing max_len characters and attempt to copy it over to tmpbuf, a buffer with * N bytes of capacity. It will return false if N is too small (smaller than max_len) of if it is zero. + * Trailing whitespace, commas, and record separators (0x1E) do not count toward max_len. * The buffer (tmpbuf) is padded with space characters. */ simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept; @@ -225150,8 +221083,11 @@ class json_iterator { /** * Updates this json iterator so that it is back at the beginning of the document, * as if it had just been created. + * + * An iterator that is no longer alive (e.g., abandoned after a fatal error) has no + * document to go back to: it is left as is and its error is returned. */ - inline void rewind() noexcept; + simdjson_inline error_code rewind() noexcept; /** * This checks whether the {,},[,] are balanced so that the document * ends with proper zero depth. This requires scanning the whole document @@ -225506,6 +221442,28 @@ class raw_json_string { private: + /** + * Returns true if target ends with an odd number of backslashes. The last one + * then escapes the quote that follows the matching bytes in the raw string, so + * that quote does not end the string. Not inlined: callers check the last byte + * first, and keys ending in a backslash are rare. + */ + static simdjson_never_inline bool ends_with_unpaired_backslash(std::string_view target) noexcept { + size_t backslash_count{0}; + while(backslash_count < target.size() && target[target.size() - 1 - backslash_count] == '\\') { + backslash_count++; + } + return backslash_count % 2 == 1; + } + + /** + * True when a byte-for-byte match must be rejected because target escapes the + * closing quote. The last-byte test keeps the counting loop off the common path. + */ + static simdjson_inline bool escapes_trailing_quote(std::string_view target) noexcept { + return simdjson_unlikely(!target.empty() && target[target.size() - 1] == '\\') + && ends_with_unpaired_backslash(target); + } /** * This will set the inner pointer to zero, effectively making @@ -226079,6 +222037,7 @@ struct simdjson_result : public rvv_vls::implementati /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ @@ -226271,6 +222230,13 @@ class array { */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + /** * Begin array iteration. * @@ -227280,6 +223246,9 @@ class document { * Reset the iterator inside the document instance so we are pointing back at the * beginning of the document, as if it had just been created. It invalidates all * values, objects and arrays that you have created so far (including unescaped strings). + * + * A document that is no longer alive (see is_alive()) cannot be rewound: it is + * left as is. */ inline void rewind() noexcept; /** @@ -227353,6 +223322,8 @@ class document { * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; @@ -227376,6 +223347,8 @@ class document { * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array + * - the fatal error (e.g., TAPE_ERROR) that ended an earlier call, as the document can + * no longer be rewound (see is_alive()). */ simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -227402,7 +223375,9 @@ class document { /** * Consumes the document and returns a string_view instance corresponding to the * document as represented in JSON. It points inside the original byte array containing - * the JSON document. + * the JSON document. It starts at the first byte of the document and ends at its last + * byte: it does not include the white space that follows the document, nor, in a + * document_stream, the separators or the documents that follow it. */ simdjson_inline simdjson_result raw_json() noexcept; @@ -227729,8 +223704,6 @@ struct simdjson_result : public rvv_vls::implementa using rvv_vls::implementation_simdjson_result_base::operator*; using rvv_vls::implementation_simdjson_result_base::operator->; - template ::value == false>::type> - explicit simdjson_inline operator T() noexcept(false); simdjson_inline operator rvv_vls::ondemand::array() & noexcept(false); simdjson_inline operator rvv_vls::ondemand::object() & noexcept(false); explicit simdjson_inline operator uint64_t() noexcept(false); @@ -227765,6 +223738,8 @@ struct simdjson_result : public rvv_vls::implementa simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -227891,6 +223866,8 @@ struct simdjson_result : public rvv_vls:: simdjson_inline simdjson_result get_number() noexcept; /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ simdjson_inline simdjson_result raw_json_token() noexcept; + /** @copydoc simdjson_inline simdjson_result document::raw_json() noexcept */ + simdjson_inline simdjson_result raw_json() noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; @@ -229852,6 +225829,7 @@ concept key_selector_type = key_selector_detail::is_key_selector::value; /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/implementation_simdjson_result_base.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/value_iterator.h" */ +/* amalgamation skipped (editor-only): #include "simdjson/jsonpathutil.h" */ /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/key_selector.h" */ /* amalgamation skipped (editor-only): #include */ /* amalgamation skipped (editor-only): #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS */ @@ -230396,6 +226374,13 @@ class object { simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; + /** + * The recursive step of for_each_at_path_with_wildcard: matches + * path[index] at this level and what follows below it. + */ + template + inline error_code for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept; + value_iterator iter{}; #if SIMDJSON_DEVELOPMENT_CHECKS bool locked{false}; @@ -231260,6 +227245,14 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept(false) { std::is_default_constructible_v, "The specified type inside the unique_ptr must default constructible."); + // A null pointer is serialized as null. + bool is_null_value; + SIMDJSON_TRY( val.is_null().get(is_null_value) ); + if (is_null_value) { + out.reset(); + return SUCCESS; + } + // Own the allocation before get(): a user tag_invoke may throw. std::unique_ptr ptr(new (std::nothrow) element_type()); if (!ptr) { @@ -231308,6 +227301,8 @@ constexpr bool user_defined_type = (std::is_class_v #endif // SIMDJSON_SUPPORTS_CHAR8_T && !concepts::optional_type && !concepts::appendable_containers +// Smart pointers are handled by the generic smart-pointer tag_invoke. +&& !concepts::smart_pointer // simdjson's own types (array, object, value, raw_json_string, number, // document, document_reference) have dedicated get() specializations and // must never go through reflection. @@ -231900,251 +227895,8 @@ error_code tag_invoke(deserialize_tag, ValT &val, T &out) noexcept { #endif } -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::unique_ptr &out) noexcept(false) { - std::unique_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - -template - requires(user_defined_type>) -error_code tag_invoke(deserialize_tag, simdjson_value &val, std::shared_ptr &out) noexcept(false) { - std::shared_ptr ptr(new (std::nothrow) T()); - if (!ptr) { - return MEMALLOC; - } - SIMDJSON_TRY(val.get(*ptr)); - out = std::move(ptr); - return SUCCESS; -} - #endif // SIMDJSON_STATIC_REFLECTION -//////////////////////////////////////// -// Unique pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_unique(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Shared pointers -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_bool().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_int64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_uint64().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_double().get(*out)); - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); - return SUCCESS; - } - if (!out) { - out = std::make_shared(); - if (!out) { return MEMALLOC; } - } - SIMDJSON_TRY(val.get_string().get(*out)); - return SUCCESS; -} - - -//////////////////////////////////////// -// Explicit optional specializations -//////////////////////////////////////// - -//////////////////////////////////////// -// Explicit smart pointer specializations for string and int types -//////////////////////////////////////// -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::shared_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_shared(); - } - std::string_view str; - SIMDJSON_TRY(val.get_string().get(str)); - *out = std::string{str}; - return SUCCESS; -} - -error_code tag_invoke(deserialize_tag, auto &val, std::unique_ptr &out) noexcept { - // Check if the value is null - bool is_null_value; - SIMDJSON_TRY( val.is_null().get(is_null_value) ); - if (is_null_value) { - out.reset(); // Set to nullptr - return SUCCESS; - } - - if (!out) { - out = std::make_unique(); - } - int64_t temp; - SIMDJSON_TRY(val.get_int64().get(temp)); - *out = static_cast(temp); - return SUCCESS; -} - } // namespace simdjson #endif // SIMDJSON_ONDEMAND_DESERIALIZE_H @@ -232259,10 +228011,9 @@ simdjson_inline simdjson_result array::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; + // After 'consume()', the last token consumed is the closing bracket: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -232346,12 +228097,23 @@ template template #endif inline error_code array::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every element reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code array::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed - // bracket-quoted keys). Without this check, an empty key is treated as index - // 0 and remaining_path may be unchanged, causing infinite recursion. + // bracket-quoted keys). if (key.empty()) { return INVALID_JSON_POINTER; } @@ -232360,11 +228122,11 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa for(auto element: *this) { value val; SIMDJSON_TRY(element.get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // An element that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -232383,16 +228145,16 @@ inline error_code array::for_each_at_path_with_wildcard(std::string_view json_pa value val; if (!error) { error = at(idx).get(val); } if (error) { - if (error == INDEX_OUT_OF_BOUNDS && internal::json_path_has_wildcard(remaining_path)) { + if (error == INDEX_OUT_OF_BOUNDS && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()){ + if (last){ callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -233050,6 +228812,19 @@ template template #endif inline error_code value::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { + // The path is split once: every value reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code value::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { // Every recursive step of for_each_at_path_with_wildcard goes through this // function, and each one descends one level into the document. A path with // many segments applied to a deeply nested document would otherwise recurse @@ -233058,10 +228833,16 @@ inline error_code value::for_each_at_path_with_wildcard(std::string_view json_pa json_type t; SIMDJSON_TRY(type().get(t)); switch (t) { - case json_type::array: - return (*this).get_array().for_each_at_path_with_wildcard(json_path, std::forward(callback)); - case json_type::object: - return (*this).get_object().for_each_at_path_with_wildcard(json_path, std::forward(callback)); + case json_type::array: { + array a; + SIMDJSON_TRY(get_array().get(a)); + return a.for_each_at_path_segments(path, index, callback); + } + case json_type::object: { + object o; + SIMDJSON_TRY(get_object().get(o)); + return o.for_each_at_path_segments(path, index, callback); + } default: return INVALID_JSON_POINTER; } @@ -233434,7 +229215,8 @@ simdjson_inline document document::start(json_iterator &&iter) noexcept { } inline void document::rewind() noexcept { - iter.rewind(); + // A document that is no longer alive is left as it is (see is_alive()). + static_cast(iter.rewind()); } inline std::string document::to_debug_string() noexcept { @@ -233734,7 +229516,13 @@ simdjson_warn_unused simdjson_inline error_code document::consume() noexcept { auto error = is_scalar().get(scalar); if(error) { return error; } if(scalar) { - iter.return_current_and_advance(); + // Step over the scalar and leave depth 0, as skip_child() does: otherwise a + // document_stream takes the next document as the rest of this one. At depth + // 0 the scalar has already been consumed (e.g., by get_int64()). + if(iter.depth() > 0) { + iter.return_current_and_advance(); + iter.ascend_to(0); + } return SUCCESS; } error = iter.skip_child(0); @@ -233747,10 +229535,36 @@ simdjson_inline simdjson_result document::raw_json() noexcept const uint8_t * starting_point{_iter.peek_start()}; auto error = consume(); if(error) { return error; } - // After 'consume()', we could be left pointing just beyond the document, but that - // is ok because we are not going to dereference the final pointer position, we just - // use it to compute the length in bytes. - const uint8_t * final_point{iter.unsafe_pointer()}; + // After 'consume()', the iterator points at the token after the document. In a + // document_stream, that is the next document, past white space and separators, or + // the end of the input when a truncated document follows. We end on the last byte + // of this document instead. + const uint8_t * final_point; + if(*starting_point == '{' || *starting_point == '[') { + // The last token consumed is the closing brace or bracket. + final_point = iter.token.peek(-1) + 1; + } else { + // A scalar is a single token: a string ends at its closing quote, anything else + // at the first white space, structural character, quote, RS (0x1E) or NUL. + const uint8_t * next_token{iter.unsafe_pointer()}; + final_point = starting_point + 1; + if(*starting_point == '"') { + while(final_point < next_token) { + uint8_t c = *final_point++; + if(c == '\\') { + final_point++; + } else if(c == '"') { + break; + } + } + if(final_point > next_token) { final_point = next_token; } + } else { + while(final_point < next_token && !jsoncharutils::is_structural_or_whitespace(*final_point) + && *final_point != '"' && *final_point != 0x1E && *final_point != 0) { + final_point++; + } + } + } return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -233797,7 +229611,9 @@ simdjson_inline simdjson_result document::raw_json_token() noe } simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_pointer is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_pointer.empty()) { return this->get_value(); } @@ -233815,7 +229631,9 @@ simdjson_inline simdjson_result document::at_pointer(std::string_view jso } simdjson_inline simdjson_result document::at_path(std::string_view json_path) noexcept { - rewind(); // Rewind the document each time at_pointer is called + // Rewind the document each time at_path is called. After a fatal error, + // the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return this->get_value(); } @@ -233838,7 +229656,9 @@ template template #endif simdjson_inline error_code document::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - rewind(); // Rewind the document each time for_each_at_path_with_wildcard is called + // Rewind the document each time for_each_at_path_with_wildcard is called. After + // a fatal error, the document cannot be walked again: we report the error instead. + SIMDJSON_TRY(iter.rewind()); if (json_path.empty()) { return INVALID_JSON_POINTER; } @@ -234176,11 +229996,6 @@ simdjson_inline simdjson_result simdjson_result::value == false>::type> -simdjson_inline simdjson_result::operator T() noexcept(false) { - if (error()) { throw simdjson_error(error()); } - return first; -} simdjson_inline simdjson_result::operator rvv_vls::ondemand::array() & noexcept(false) { if (error()) { throw simdjson_error(error()); } return first; @@ -234241,6 +230056,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -234727,6 +230547,11 @@ simdjson_inline simdjson_result simdjson_result simdjson_result::raw_json() noexcept { + if (error()) { return error(); } + return first.raw_json(); +} + simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { if (error()) { return error(); } return first.at_pointer(json_pointer); @@ -235118,6 +230943,13 @@ simdjson_inline bool document_stream::skip_to_delimiter(uint8_t delimiter) noexc } inline void document_stream::next_document() noexcept { + // Iteration stops at the first document that met a fatal error. The error + // may have abandoned the document's iterator, which then no longer has a + // parser to walk the rest of the document with. + if (!doc.iter.is_alive() || doc.iter.error) { + error = doc.iter.error ? doc.iter.error : TAPE_ERROR; + return; + } // A delimiter that cannot occur inside a document tells us where the current // one ends, so we can jump there instead of walking every structural. Only // valid while the iterator is still inside the document: a consumed document @@ -235267,7 +231099,10 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc cur_struct_index++; } - while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { + // The structural index at n_structural_indexes is the end of the batch, not + // a token: it must not be read as a closing bracket. + const int64_t n_structural_indexes = static_cast(stream->parser->implementation->n_structural_indexes); + while (cur_struct_index < n_structural_indexes) { switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { case '{': case '[': depth++; @@ -235279,6 +231114,11 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc if (depth == 0) { break; } cur_struct_index++; } + // An invalid last document such as "[1 2" is never closed: it runs to the + // end of the batch, and not one byte past it. + if (cur_struct_index >= n_structural_indexes) { + return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index] - current_index()); + } return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; } @@ -235295,7 +231135,12 @@ inline void document_stream::load_from_stage1_thread() noexcept { // enough memory to swap to, as well. std::swap(stage1_thread_parser,*parser); error = stage1_thread_error; - if (error) { return; } + // An EMPTY batch (no complete document in it, e.g. a run of white space at + // least batch_size long) is not the end of the stream: next() moves on to + // the batch after it, so queue that batch now as we would after a success. + // Returning here would leave no job for the next call, which then swaps + // the stale previous batch back in and ends the stream early. + if (error && error != EMPTY) { return; } // If there's anything left, start the stage 1 thread! if (next_batch_start() < len) { @@ -235407,12 +231252,14 @@ simdjson_inline raw_json_string field::key() const noexcept { simdjson_inline std::string_view field::key_raw_json_token() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(-1) - first.buf + 1); + // The ':' is the token just before the value. Use the value's start position + // rather than the current position, which moves once the value is consumed. + return std::string_view(reinterpret_cast(first.buf-1), second.iter._json_iter->token.peek(second.iter.start_position() - 1) - first.buf + 1); } simdjson_inline std::string_view field::escaped_key() const noexcept { SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. - auto end_quote = second.iter._json_iter->token.peek(-1); + auto end_quote = second.iter._json_iter->token.peek(second.iter.start_position() - 1); while(*end_quote != '"') end_quote--; return std::string_view(reinterpret_cast(first.buf), end_quote - first.buf); } @@ -235583,11 +231430,14 @@ simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parse } #endif // SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON -inline void json_iterator::rewind() noexcept { +simdjson_inline error_code json_iterator::rewind() noexcept { + // abandon() let go of the parser: the iterator cannot be walked again. + if (!is_alive()) { return error ? error : UNINITIALIZED; } token.set_position( root_position() ); logger::log_headers(); // We start again _string_buf_loc = parser->string_buf.get(); _depth = 1; + return SUCCESS; } inline bool json_iterator::balanced() const noexcept { @@ -235611,6 +231461,18 @@ inline bool json_iterator::balanced() const noexcept { } +// Depth change of each structural character when skipping: +1 for [ and {, -1 for ] and }. +static constexpr int8_t skip_depth_delta[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,-1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + // GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller // relating depth and parent_depth, which is a desired effect. The warning does not show up if the // skip_child() function is not marked inline). @@ -235666,7 +231528,30 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_ break; } - // Now that we've considered the first value, we only increment/decrement for arrays/objects + // Now that we've considered the first value, we only increment/decrement for arrays/objects. + // + // Without logging, look up the depth change of each token in a table instead of branching + // on it: the next byte is hard to predict, so a switch mispredicts often. The loop also keeps + // the position and depth in locals, because the byte loads are char loads that may alias + // token._position and _depth, which would force a store of both on every token. + if (!logger::LOG_ENABLED) { + token_position pos = token.position(); + const token_position end = end_position(); + const uint8_t *const json = token.buf; + depth_t depth = _depth; + while (pos < end) { + depth += skip_depth_delta[json[*pos++]]; + if (depth <= parent_depth) { + token.set_position(pos); + _depth = depth; + return SUCCESS; + } + } + token.set_position(pos); + _depth = depth; + return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "not enough close braces"); + } + while (position() < end_position()) { switch (*return_current_and_advance()) { case '[': case '{': @@ -235951,6 +231836,17 @@ simdjson_warn_unused simdjson_inline error_code json_iterator::optional_error(er simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t *tmpbuf, size_t N) noexcept { // This function is not expected to be called in performance-sensitive settings. + // peek_root_length() stops at the next structural index. Stage 1 omits the + // comma or RS (0x1E) that separates stream documents, so that byte and the + // whitespace around it remain in the span. RS is not a valid number finisher. + while (max_len > 0) { + const uint8_t last = json[max_len - 1]; + if (last != ' ' && last != '\t' && last != '\n' && last != '\r' && + last != ',' && last != 0x1E) { + break; + } + max_len--; + } // Let us guard against silly cases: if((N < max_len) || (N == 0)) { return false; } // Copy to the buffer. @@ -236592,7 +232488,9 @@ simdjson_inline simdjson_result object::raw_json() noexcept { const uint8_t * starting_point{iter.peek_start()}; auto error = consume(); if(error) { return error; } - const uint8_t * final_point{iter._json_iter->peek()}; + // After 'consume()', the last token consumed is the closing brace: we end on it + // rather than on the next token, which may follow white space. + const uint8_t * final_point{iter._json_iter->token.peek(-1) + 1}; return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); } @@ -236685,9 +232583,21 @@ template template #endif inline error_code object::for_each_at_path_with_wildcard(std::string_view json_path, Func&& callback) noexcept { - auto result_pair = get_next_key_and_json_path(json_path); - std::string_view key = result_pair.first; - std::string_view remaining_path = result_pair.second; + // The path is split once: every field reached by a wildcard sees the same + // remaining path, so re-parsing it at each one would be quadratic. + const std::vector path = internal::split_json_path(json_path); + // A path that cannot be parsed matches nothing, whatever the document: say + // so rather than letting the walk skip it as a mismatch below a wildcard. + if (path.back().key.empty()) { + return INVALID_JSON_POINTER; + } + return for_each_at_path_segments(path, 0, callback); +} + +template +inline error_code object::for_each_at_path_segments(const std::vector &path, size_t index, Func &callback) noexcept { + std::string_view key = path[index].key; + const bool last = index + 1 == path.size(); // Empty key means the path segment could not be parsed (including malformed // bracket-quoted keys). Match DOM at_path_with_wildcard and avoid treating // "no progress" as a recursive lookup of "". @@ -236700,11 +232610,11 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p for (auto field : *this) { value val; SIMDJSON_TRY(field.value().get(val)); - if (remaining_path.empty()) { + if (last) { callback(val); } else { // A field that does not match the rest of the path is skipped. - error_code err = val.for_each_at_path_with_wildcard(remaining_path, callback); + error_code err = val.for_each_at_path_segments(path, index + 1, callback); if (err && !internal::is_matching_failure_error(err)) { return err; } } } @@ -236713,17 +232623,17 @@ inline error_code object::for_each_at_path_with_wildcard(std::string_view json_p value val; error_code error = find_field(key).get(val); if (error) { - if (error == NO_SUCH_FIELD && internal::json_path_has_wildcard(remaining_path)) { + if (error == NO_SUCH_FIELD && path[index].wildcard_follows) { return SUCCESS; } return error; } - if (remaining_path.empty()) { + if (last) { callback(val); return SUCCESS; } else { - return val.for_each_at_path_with_wildcard(remaining_path, callback); + return val.for_each_at_path_segments(path, index + 1, callback); } } } @@ -237376,6 +233286,7 @@ simdjson_inline simdjson_warn_unused bool parser::string_buffer_overflow(const u simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -237393,6 +233304,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(p #ifdef SIMDJSON_EXPERIMENTAL_ALLOW_INCOMPLETE_JSON simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_allow_incomplete_json(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); _document_len = json.length(); @@ -237448,6 +233360,7 @@ simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(c simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { if (!json.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + json.use_trailing_whitespace_as_padding(); json.remove_utf8_bom(); @@ -237474,6 +233387,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { @@ -237509,6 +233423,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, bool allow_comma_separated) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, allow_comma_separated); } inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept { @@ -237554,6 +233469,7 @@ inline simdjson_result parser::iterate_many(const char *buf, si } inline simdjson_result parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept { if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; } + s.use_trailing_whitespace_as_padding(); return iterate_many(s.data(), s.length(), batch_size, format); } inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept { @@ -237708,17 +233624,18 @@ simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* t return true; } - simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { // If we are going to call memcmp, then we must know something about the length of the raw_json_string. - return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { // Assumptions: does not contain unescaped quote characters("), and // the raw content is quote terminated within a valid JSON string. if(target.size() <= SIMDJSON_PADDING) { - return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); + return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()) + && !escapes_trailing_quote(target); } // Past SIMDJSON_PADDING we can no longer rely on the padding to keep the // comparison in bounds, so we must stop at the quote terminating the key. @@ -237744,7 +233661,7 @@ simdjson_inline bool raw_json_string::is_equal(std::string_view target) const no escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -237757,7 +233674,7 @@ simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const for(;target[pos];pos++) { if(r[pos] != target[pos]) { return false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escapes_trailing_quote(std::string_view(target, pos))) { return false; } return true; } @@ -237782,7 +233699,7 @@ simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcep escaping = false; } } - if(r[pos] != '"') { return false; } + if((r[pos] != '"') || escaping) { return false; } return true; } @@ -237894,7 +233811,11 @@ simdjson_inline uint32_t token_iterator::peek_index(token_position position) con return *position; } simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { - return *(position+1) - *position; + // In the last batch of a document_stream, the index past the end of the + // structural indexes holds the start of the truncated bytes (see + // document_stream::truncated_bytes()), which can come before the end of the + // input: a token at the end is then empty. + return *(position+1) > *position ? *(position+1) - *position : 0; } simdjson_inline uint32_t token_iterator::peek_root_length(token_position position) const noexcept { @@ -237909,7 +233830,7 @@ simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcep return *(_position+delta); } simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { - return *(_position+delta+1) - *(_position+delta); + return peek_length(_position+delta); } simdjson_inline token_position token_iterator::position() const noexcept { @@ -237968,6 +233889,8 @@ simdjson_inline simdjson_result::simdjson_res /* amalgamation skipped (editor-only): #include "simdjson/generic/ondemand/raw_json_string-inl.h" */ /* amalgamation skipped (editor-only): #endif // SIMDJSON_CONDITIONAL_INCLUDE */ +#include // std::memset (zeroing the root-number scratch padding) + namespace simdjson { namespace rvv_vls { namespace ondemand { @@ -238014,6 +233937,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { assert_at_container_start(); #if SIMDJSON_DEVELOPMENT_CHECKS + // The development checks record the start of every open container in a + // buffer of max_depth() entries: a document nested deeper than that is + // rejected, as the DOM parser does. + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } _json_iter->set_start_position(_depth, start_position()); #endif if (*_json_iter->peek() == '}') { @@ -238423,6 +234350,10 @@ inline std::string value_iterator::to_string() const noexcept { simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { assert_at_container_start(); +#if SIMDJSON_DEVELOPMENT_CHECKS + // See started_object(). + if (size_t(_depth) >= _json_iter->parser->max_depth()) { return report_error(DEPTH_ERROR, "Exceeded max depth!"); } +#endif if (*_json_iter->peek() == ']') { logger::log_value(*_json_iter, "empty array"); _json_iter->return_current_and_advance(); @@ -238628,8 +234559,10 @@ simdjson_inline simdjson_result value_iterator::get_root_n // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -238650,8 +234583,10 @@ simdjson_inline simdjson_result value_iterator::get_root_number(bool che // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. // NOTE: the current approach doesn't work for very big integer numbers containing more than 1074 digits. - uint8_t tmpbuf[1074+8+1+1]; - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { if(numberparsing::check_if_integer(json, max_len)) { if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -238721,10 +234656,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::g simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("uint64"); - uint8_t tmpbuf[20+1+1]{}; // <20 digits> is the longest possible unsigned integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]{}; // "<20 digits>" is the longest possible unsigned integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } auto result = numberparsing::parse_unsigned_in_string(tmpbuf); @@ -238754,10 +234689,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::ge simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("int64"); - uint8_t tmpbuf[20+1+1]; // -<19 digits> is the longest possible integer - tmpbuf[20+1] = '\0'; // make sure that buffer is always null terminated. - if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+1)) { - logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); + uint8_t tmpbuf[20+2+1]; // "-<19 digits>" is the longest possible integer + tmpbuf[20+2] = '\0'; // make sure that buffer is always null terminated. + if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 20+2)) { + logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 22 characters"); return NUMBER_ERROR; } @@ -238774,8 +234709,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -238794,8 +234731,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest // number: -0.e-308. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -238814,8 +234753,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ // We use the same buffer size as get_root_double: the number of significant // digits that matter is smaller for binary32, but the JSON document may still // spell out a long number that we must parse (and round) faithfully. - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -238831,8 +234772,10 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_float_in_string(bool check_trailing) noexcept { auto max_len = peek_root_length(); auto json = peek_root_scalar("float"); - uint8_t tmpbuf[1074+8+1+1]; // +1 for null termination. - tmpbuf[1074+8+1] = '\0'; // make sure that buffer is always null terminated. + // +SIMDJSON_PADDING: the digit scan reads up to 7 bytes past the terminator. + uint8_t tmpbuf[1074+8+1+1+SIMDJSON_PADDING]; // +1 for null termination. + // Null terminate, and zero the padding as allocate_padded_buffer does. + std::memset(tmpbuf+1074+8+1, 0, 1+SIMDJSON_PADDING); if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf, 1074+8+1)) { logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); return NUMBER_ERROR; @@ -238853,7 +234796,7 @@ simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_r // "true" and "false" are 4 and 5 characters long, respectively. bool value_true = (max_len >= 4 && !atomparsing::str4ncmp(json, "true") && (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); - bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "false") && + bool value_false = (max_len >= 5 && !atomparsing::str4ncmp(json, "fals") && json[4] == 'e' && (max_len == 5 || jsoncharutils::is_structural_or_whitespace(json[5]))); if(value_true == false && value_false == false) { return incorrect_type_error("Not a boolean"); } if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } @@ -239493,49 +235436,6 @@ namespace json_path { using ::simdjson::rvv_vls::ondemand::value; -// Path step types -enum class step_type { - field, // .field_name or ["field_name"] - array_index // [index] -}; - -// Represents a single step in a JSON path -template -struct path_step { - step_type type; - char key[N]; // Field name (empty for array indices) - std::size_t index; // Array index (0 for field access) - - constexpr path_step(step_type t, const char (&k)[N], std::size_t idx = 0) - : type(t), index(idx) { - for (std::size_t i = 0; i < N; ++i) { - key[i] = k[i]; - } - } - - constexpr std::string_view key_view() const { - return {key, N - 1}; - } -}; - -// Helper to create field step -template -consteval auto make_field_step(const char (&name)[N]) { - return path_step(step_type::field, name, 0); -} - -// Helper to create array index step -consteval auto make_index_step(std::size_t idx) { - return path_step<1>(step_type::array_index, "", idx); -} - -// Parse state for compile-time JSON path parsing -struct parse_result { - bool success; - std::size_t pos; - std::string_view error_msg; -}; - // Compile-time JSON path parser // Supports subset: .field, ["field"], [index], nested combinations template @@ -239549,77 +235449,6 @@ struct json_path_parser { } return 0; } - - // Count the number of steps in the path at compile time - static consteval std::size_t count_steps() { - std::size_t count = 0; - std::size_t i = skip_root(); - - while (i < path_str.size()) { - if (path_str[i] == '.') { - // Field access: .field - ++i; - if (i >= path_str.size()) break; - - // Skip field name - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[') { - ++i; - } - ++count; - } else if (path_str[i] == '[') { - // Array or bracket notation - ++i; - if (i >= path_str.size()) break; - - if (path_str[i] == '"' || path_str[i] == '\'') { - // Field access: ["field"] or ['field'] - char quote = path_str[i]; - ++i; - while (i < path_str.size() && path_str[i] != quote) { - ++i; - } - if (i < path_str.size()) ++i; // skip closing quote - if (i < path_str.size() && path_str[i] == ']') ++i; - } else { - // Array index: [0], [123] - while (i < path_str.size() && path_str[i] != ']') { - ++i; - } - if (i < path_str.size()) ++i; // skip ] - } - ++count; - } else { - ++i; - } - } - - return count; - } - - // Parse a field name at compile time - static consteval std::size_t parse_field_name(std::size_t start, char* out, std::size_t max_len) { - std::size_t len = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] != '.' && path_str[i] != '[' && len < max_len - 1) { - out[len++] = path_str[i++]; - } - out[len] = '\0'; - return i; - } - - // Parse an array index at compile time - static consteval std::pair parse_array_index(std::size_t start) { - std::size_t index = 0; - std::size_t i = start; - - while (i < path_str.size() && path_str[i] >= '0' && path_str[i] <= '9') { - index = index * 10 + (path_str[i] - '0'); - ++i; - } - - return {i, index}; - } }; // Compile-time path accessor generator @@ -239628,7 +235457,6 @@ struct path_accessor { using value = ::simdjson::rvv_vls::ondemand::value; static constexpr auto parser = json_path_parser(); - static constexpr std::size_t num_steps = parser.count_steps(); static constexpr std::string_view path_view = Path.view(); // Compile-time accessor generation @@ -239743,93 +235571,6 @@ struct path_accessor { return current_type; } -private: - // Walk path and extract directly into final field using compile-time reflection - template - static inline error_code extract_with_reflection(simdjson_result current, TargetType& target_ref) noexcept { - if (current.error()) return current.error(); - - // Base case: end of path - extract into target - if constexpr (PathPos >= path_view.size()) { - return current.get(target_ref); - } - // Field access: .field_name - else if constexpr (path_view[PathPos] == '.') { - constexpr auto field_info = parse_next_field(PathPos); - constexpr std::string_view field_name = std::get<0>(field_info); - constexpr std::size_t next_pos = std::get<1>(field_info); - - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } - // Bracket notation: [index] or ["field"] - else if constexpr (path_view[PathPos] == '[') { - constexpr auto bracket_info = parse_bracket(PathPos); - constexpr bool is_field = std::get<0>(bracket_info); - constexpr std::size_t next_pos = std::get<2>(bracket_info); - - if constexpr (is_field) { - constexpr std::string_view field_name = std::get<1>(bracket_info); - constexpr auto member_info = find_member_by_name(CurrentType, field_name); - static_assert(member_info != ^^void, "Field not found in struct"); - constexpr auto member_type = std::meta::type_of(member_info); - - auto obj_result = current.get_object(); - if (obj_result.error()) return obj_result.error(); - auto obj = obj_result.value_unsafe(); - auto field_value = obj.find_field_unordered(field_name); - - if constexpr (next_pos >= path_view.size()) { - return field_value.get(target_ref); - } else { - return extract_with_reflection(field_value, target_ref); - } - } else { - constexpr std::size_t index = std::get<3>(bracket_info); - constexpr auto elem_type = get_element_type_reflected(CurrentType); - static_assert(elem_type != ^^void, "Could not determine array element type"); - - auto arr_result = current.get_array(); - if (arr_result.error()) return arr_result.error(); - auto arr = arr_result.value_unsafe(); - auto elem_value = arr.at(index); - - if constexpr (next_pos >= path_view.size()) { - return elem_value.get(target_ref); - } else { - return extract_with_reflection(elem_value, target_ref); - } - } - } - // Skip unexpected characters and continue - else { - return extract_with_reflection(current, target_ref); - } - } - - // Find member by name in reflected type - static consteval std::meta::info find_member_by_name(std::meta::info type_refl, std::string_view name) { - auto members = std::meta::nonstatic_data_members_of(type_refl, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == name) { - return mem; - } - } - } - // Generate compile-time accessor code by walking the path template static inline simdjson_result access_impl(simdjson_result current) noexcept { @@ -239959,55 +235700,6 @@ struct path_accessor { } private: - // Check if type has member with given name - template - static consteval bool has_member(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return true; - } - } - return false; - } - - // Get type of member by name - template - static consteval auto get_member_type(std::string_view member_name) { - constexpr auto members = std::meta::nonstatic_data_members_of(^^Type, std::meta::access_context::unchecked()); - for (auto mem : members) { - if (std::meta::identifier_of(mem) == member_name) { - return std::meta::type_of(mem); - } - } - return ^^void; - } - - // Check if non-reflected type is array-like - template - static consteval bool is_container_type() { - using BaseType = std::remove_cvref_t; - if constexpr (requires { typename BaseType::value_type; }) { - return true; - } - if constexpr (std::is_array_v) { - return true; - } - return false; - } - - // Extract element type from non-reflected container - template - using extract_element_type = std::conditional_t< - requires { typename std::remove_cvref_t::value_type; }, - typename std::remove_cvref_t::value_type, - std::conditional_t< - std::is_array_v>, - std::remove_extent_t>, - void - > - >; - // Validate path matches struct definition static consteval bool validate_path() { if constexpr (!std::is_class_v) { @@ -240416,9 +236108,9 @@ namespace simdjson { inline std::string to_json_string(const T& obj) { builder::string_builder str_builder; append(str_builder, obj); - std::string_view view; - if (str_builder.view().get(view) == SUCCESS) { - return std::string(view); + std::string result; + if (str_builder.move_to(result) == SUCCESS) { + return result; } return ""; } @@ -245906,17 +241598,12 @@ from_chars_advanced(UC const *first, UC const *last, T &value, #endif /* end file simdjson/internal/fast_float.h */ -#include -#include -#include -#include -#include - #include #include #include #include #include +#include #include #include #include @@ -245978,34 +241665,6 @@ consteval auto make_data_member_options(auto&& name_str) { return options; } -// JSON string may contain embedded nulls, and C++26 reflection does not yet -// support std::string_view as a data member type. As a workaround, we define -// a custom type that holds a const char* and a size. -template struct fixed_json_string { - // Statically-allocated array to hold the characters and a null terminator - static constexpr char inner_data[] = {Vals..., '\0'}; - - // Constant for the length of the string view (excluding the null terminator) - static constexpr std::size_t inner_size = sizeof...(Vals); - - // The std::string_view over the data. - // We use data and size to avoid including the null terminator in the view. - static constexpr std::string_view view = {inner_data, inner_size}; - - constexpr operator std::string_view() { return view; } - constexpr const char *c_str() { return view.data(); } - constexpr const char *data() { return view.data(); } - constexpr size_t size() { return view.size(); } -}; - -consteval std::meta::info to_fixed_json_string(std::string_view in) { - std::vector Args = {}; - for (char c : in) { - Args.push_back(std::meta::reflect_constant(c)); - } - return std::meta::substitute(^^fixed_json_string, Args); -} - /** * @brief Helper struct for substitute() pattern */ From 38da2b9ad980dcb55616b3fe8ed401a6372dcd3c Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Mon, 2 Nov 2026 19:53:29 -0500 Subject: [PATCH 084/195] deps: update perfetto to 59.1 PR-URL: https://github.com/nodejs/node/pull/67171 Reviewed-By: Colin Ihrig Reviewed-By: Antoine du Hamel --- deps/perfetto/LICENSE | 2 +- deps/perfetto/VERSION | 2 +- deps/perfetto/sdk/LICENSE | 285 + deps/perfetto/sdk/perfetto.cc | 83928 ++++++++++++++++++-------------- deps/perfetto/sdk/perfetto.h | 3009 +- 5 files changed, 49573 insertions(+), 37653 deletions(-) create mode 100644 deps/perfetto/sdk/LICENSE diff --git a/deps/perfetto/LICENSE b/deps/perfetto/LICENSE index 681d40008ee3..2db252fd570b 100644 --- a/deps/perfetto/LICENSE +++ b/deps/perfetto/LICENSE @@ -226,7 +226,7 @@ Files: src/trace_processor/perfetto_sql/stdlib/chromium/*, protos/third_party/ch ------------------ -Files: src/trace_processor/perfetto_sql/syntaqlite/syntaqlite_perfetto.{c, h} +Files: src/perfetto_sql/syntaqlite/syntaqlite_perfetto.{c, h} Copyright 2025 The syntaqlite Authors. All rights reserved. diff --git a/deps/perfetto/VERSION b/deps/perfetto/VERSION index 5132e635a5f8..2246a13a3daf 100644 --- a/deps/perfetto/VERSION +++ b/deps/perfetto/VERSION @@ -1 +1 @@ -58.2 +59.1 diff --git a/deps/perfetto/sdk/LICENSE b/deps/perfetto/sdk/LICENSE new file mode 100644 index 000000000000..2db252fd570b --- /dev/null +++ b/deps/perfetto/sdk/LICENSE @@ -0,0 +1,285 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + +------------------ + +Files: * except those files noted below + + Copyright (c) 2017, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +------------------ + +Files: src/trace_processor/perfetto_sql/stdlib/chromium/*, protos/third_party/chromium/*, test/trace_processor/diff_tests/stdlib/chrome/* + + Copyright 2015 The Chromium Authors + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +------------------ + +Files: src/perfetto_sql/syntaqlite/syntaqlite_perfetto.{c, h} + + Copyright 2025 The syntaqlite Authors. All rights reserved. + + Machine-generated amalgamation of syntaqlite runtime + Perfetto dialect + sources (https://github.com/LalitMaganti/syntaqlite). Portions derive + from SQLite's public-domain `parse.y` grammar. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +------------------ + +Files: src/trace_processor/perfetto_sql/preprocessor/preprocessor_grammar.{c, h} + +The author disclaims copyright to this source code. In place of a legal notice, here is a blessing: + +May you do good and not evil. +May you find forgiveness for yourself and forgive others. +May you share freely, never taking more than you give. + + +------------------ + +Files: src/base/intrusive_tree.cc + +$OpenBSD: tree.h,v 1.31 2023/03/08 04:43:09 guenther Exp $ + +Copyright 2002 Niels Provos +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/perfetto/sdk/perfetto.cc b/deps/perfetto/sdk/perfetto.cc index 739d48322046..3aa0cd8e36be 100644 --- a/deps/perfetto/sdk/perfetto.cc +++ b/deps/perfetto/sdk/perfetto.cc @@ -122,6 +122,14 @@ void AfterMaybeBlockingSyscall() {} #include +// The __attribute__((constructor)) below is intentional: this function runs at +// load time on purpose to verify the CPU supports the extensions the binary is +// built with. -Wglobal-constructors (enabled by -Weverything) would otherwise +// flag it. +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + // gen_amalgamated expanded: #include "perfetto/base/build_config.h" // gen_amalgamated expanded: #include "perfetto/base/export.h" @@ -150,7 +158,13 @@ uint32_t GetXCR0EAX() { // If we are building with -msse4 check that the CPU actually supports it. // This file must be kept in sync with gn/standalone/BUILD.gn. -void PERFETTO_EXPORT_COMPONENT __attribute__((constructor)) +// +// Priority 101 is the earliest non-reserved constructor priority. With the +// default (65535) this would run after protobuf's descriptor registration +// (priority 102), which already executes BMI2 instructions and SIGILLs on +// older CPUs before this check can print its message. See +// https://github.com/google/perfetto/issues/7504. +void PERFETTO_EXPORT_COMPONENT __attribute__((constructor(101))) CheckCpuOptimizations() { uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0; PERFETTO_GETCPUID(eax, ebx, ecx, edx, 1, 0); @@ -221,6 +235,7 @@ CheckCpuOptimizations() { #include #include #include +#include // gen_amalgamated expanded: #include "perfetto/base/build_config.h" @@ -258,6 +273,7 @@ struct SystemInfo { std::string android_storage_model; std::string android_ram_model; std::string android_serial_console; + std::vector device_tree_compatibles; }; // Returns the device's utsname information. @@ -277,9 +293,8 @@ std::string GetPerfettoMachineName(); } // namespace perfetto #endif // INCLUDE_PERFETTO_EXT_BASE_ANDROID_UTILS_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/string_utils.h -// gen_amalgamated begin header: include/perfetto/ext/base/string_view.h -// gen_amalgamated begin header: include/perfetto/ext/base/fnv_hash.h +// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h +// gen_amalgamated begin header: include/perfetto/base/status.h /* * Copyright (C) 2019 The Android Open Source Project * @@ -296,135 +311,110 @@ std::string GetPerfettoMachineName(); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ +#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ +#define INCLUDE_PERFETTO_BASE_STATUS_H_ -#include -#include +#include +#include #include -#include -#include +#include -namespace perfetto::base { +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// A helper class which computes a 64-bit hash of the input data. -// The algorithm used is FNV-1a as it is fast and easy to implement and has -// relatively few collisions. -// WARNING: This hash function should not be used for any cryptographic purpose. -class FnvHasher { - public: - // Creates an empty hash object - constexpr FnvHasher() = default; +namespace perfetto { +namespace base { - // Hashes a numeric value. - template < - typename T, - typename std::enable_if::value, bool>::type = true> - void Update(T data) { - Update(reinterpret_cast(&data), sizeof(data)); +// Represents either the success or the failure message of a function. +// This can used as the return type of functions which would usually return an +// bool for success or int for errno but also wants to add some string context +// (ususally for logging). +// +// Similar to absl::Status, an optional "payload" can also be included with more +// context about the error. This allows passing additional metadata about the +// error (e.g. location of errors, potential mitigations etc). +class PERFETTO_EXPORT_COMPONENT Status { + public: + Status() : ok_(true) {} + explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { + PERFETTO_CHECK(!message_.empty()); } - constexpr void Update(char c) { return Update(&c, 1); } - - // Using the loop instead of "Update(str, strlen(str))" to avoid looping twice - constexpr void Update(const char* str) { - for (const auto* p = str; *p; ++p) - Update(*p); - } + // Copy operations. + Status(const Status&) = default; + Status& operator=(const Status&) = default; - // Hashes a byte array. - constexpr void Update(const char* data, size_t size) { - for (size_t i = 0; i < size; i++) { - result_ ^= static_cast(data[i]); - // Note: Arithmetic overflow of unsigned integers is well defined in C++ - // standard unlike signed integers. - // https://stackoverflow.com/a/41280273 - result_ *= kFnv1a64Prime; - } - } + // Move operations. The moved-from state is valid but unspecified. + Status(Status&&) noexcept = default; + Status& operator=(Status&&) = default; - // Allow hashing anything that has `data` and `size` and has the kHashable - // trait (e.g., base::StringView). - template > - constexpr void Update(const T& t) { - if constexpr (std::is_member_function_pointer_v) { - Update(t.data(), t.size()); - } else { - Update(t.data, t.size); - } - } + bool ok() const { return ok_; } - constexpr void Update(std::string_view s) { Update(s.data(), s.size()); } + // When ok() is false this returns the error message. Returns the empty string + // otherwise. + const std::string& message() const { return message_; } + const char* c_message() const { return message_.c_str(); } - constexpr uint64_t digest() const { return result_; } + ////////////////////////////////////////////////////////////////////////////// + // Payload Management APIs + ////////////////////////////////////////////////////////////////////////////// - // Usage: - // uint64_t hashed_value = - // FnvHasher::Combine(33, false, "ABC", 458L, 3u, 'x'); - template - static constexpr uint64_t Combine(Ts&&... args) { - FnvHasher hasher; - hasher.UpdateAll(std::forward(args)...); - return hasher.digest(); - } + // Payloads can be attached to error statuses to provide additional context. + // + // Payloads are (key, value) pairs, where the key is a string acting as a + // unique "type URL" and the value is an opaque string. The "type URL" should + // be unique, follow the format of a URL and, ideally, documentation on how to + // interpret its associated data should be available. + // + // To attach a payload to a status object, call `Status::SetPayload()`. + // Similarly, to extract the payload from a status, call + // `Status::GetPayload()`. + // + // Note: the payload APIs are only meaningful to call when the status is an + // error. Otherwise, all methods are noops. - // Creates a hasher with `args` already hashed. + // Gets the payload for the given |type_url| if one exists. // - // Usage: - // FnvHasher partial = FnvHasher::CreatePartial(33, false, "ABC", 458L); - template - static constexpr FnvHasher CreatePartial(Ts&&... args) { - FnvHasher hasher; - hasher.UpdateAll(std::forward(args)...); - return hasher; - } + // Will always return std::nullopt if |ok()|. + std::optional GetPayload(std::string_view type_url) const; - // `hasher.UpdateAll(33, false, "ABC")` is shorthand for: - // `hasher.Update(33); hasher.Update(false); hasher.Update("ABC");` - constexpr void UpdateAll() {} + // Sets the payload for the given key. The key should + // + // Will always do nothing if |ok()|. + void SetPayload(std::string_view type_url, std::string value); - template - constexpr void UpdateAll(T&& arg, Ts&&... args) { - Update(arg); - UpdateAll(std::forward(args)...); - } + // Erases the payload for the given string and returns true if the payload + // existed and was erased. + // + // Will always do nothing if |ok()|. + bool ErasePayload(std::string_view type_url); private: - static constexpr uint64_t kFnv1a64OffsetBasis = 0xcbf29ce484222325; - static constexpr uint64_t kFnv1a64Prime = 0x100000001b3; + struct Payload { + std::string type_url; + std::string payload; + }; - uint64_t result_ = kFnv1a64OffsetBasis; + bool ok_ = false; + std::string message_; + std::vector payloads_; }; -// base::FnvHash uses base::FnvHasher for integer values and falls back to -// std::hash for other types. This is needed as std::hash for integers is just -// the identity function and Perfetto uses open-addressing hash table, which are -// very sensitive to hash quality and are known to degrade in performance -// when using std::hash. -template -struct FnvHash { - // Version for ints, using base::FnvHasher. - template - auto operator()(const U& x) -> - typename std::enable_if::value, size_t>::type - const { - return FnvHasher::Combine(x); - } +// Returns a status object which represents the Ok status. +inline Status OkStatus() { + return Status(); +} - // Version for non-ints, falling back to std::hash. - template - auto operator()(const U& x) -> - typename std::enable_if::value, size_t>::type - const { - return std::hash()(x); - } -}; +Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); -} // namespace perfetto::base +} // namespace base +} // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ +#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/scoped_file.h /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -439,203 +429,189 @@ struct FnvHash { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ -#define INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ +#define INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ -#include +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" + +#include + +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include // For DIR* / opendir(). +#endif -#include #include -#include -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" // gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/fnv_hash.h" +// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" namespace perfetto { namespace base { -// A string-like object that refers to a non-owned piece of memory. -// Strings are internally NOT null terminated. -class StringView { +namespace internal { +// Used for the most common cases of ScopedResource where there is only one +// invalid value. +template +struct DefaultValidityChecker { + static bool IsValid(T t) { return t != InvalidValue; } +}; +} // namespace internal + +// RAII classes for auto-releasing fds and dirs. +// if T is a pointer type, InvalidValue must be nullptr. Doing otherwise +// causes weird unexpected behaviors (See https://godbolt.org/z/5nGMW4). +template > +class ScopedResource { public: - // Allow hashing with base::FnvHash. - static constexpr bool kHashable = true; - static constexpr size_t npos = static_cast(-1); + using ValidityChecker = Checker; + static constexpr T kInvalid = InvalidValue; - StringView() : data_(nullptr), size_(0) {} - StringView(const StringView&) = default; - StringView& operator=(const StringView&) = default; - StringView(const char* data, size_t size) : data_(data), size_(size) { - PERFETTO_DCHECK(size == 0 || data != nullptr); + explicit ScopedResource(T t = InvalidValue) : t_(t) {} + ScopedResource(ScopedResource&& other) noexcept : t_(other.t_) { + other.t_ = InvalidValue; } - - // Allow implicit conversion from any class that has a |data| and |size| field - // and has the kConvertibleToStringView trait (e.g., protozero::ConstChars). - template > - StringView(const T& x) : StringView(x.data, x.size) { - PERFETTO_DCHECK(x.size == 0 || x.data != nullptr); + ScopedResource& operator=(ScopedResource&& other) { + reset(other.t_); + other.t_ = InvalidValue; + return *this; } - - // Creates a StringView from a null-terminated C string. - // Deliberately not "explicit". - StringView(const char* cstr) : data_(cstr), size_(strlen(cstr)) { - PERFETTO_DCHECK(cstr != nullptr); + T get() const { return t_; } + T operator*() const { return t_; } + explicit operator bool() const { return Checker::IsValid(t_); } + void reset(T r = InvalidValue) { + if (Checker::IsValid(t_)) { + int res = CloseFunction(t_); + if (CheckClose) + PERFETTO_CHECK(res == 0); + } + t_ = r; + } + T release() { + T t = t_; + t_ = InvalidValue; + return t; } + ~ScopedResource() { reset(InvalidValue); } - StringView(std::string_view str) : data_(str.data()), size_(str.size()) {} + private: + ScopedResource(const ScopedResource&) = delete; + ScopedResource& operator=(const ScopedResource&) = delete; + T t_; +}; - // This instead has to be explicit, as creating a StringView out of a - // std::string can be subtle. - explicit StringView(const std::string& str) - : data_(str.data()), size_(str.size()) {} +// Declared in file_utils.h. Forward declared to avoid #include cycles. +int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); - bool empty() const { return size_ == 0; } - size_t size() const { return size_; } - const char* data() const { return data_; } - const char* begin() const { return data_; } - const char* end() const { return data_ + size_; } +// Use this for file resources obtained via open() and similar APIs. +using ScopedFile = ScopedResource; +using ScopedFstream = ScopedResource; - char at(size_t pos) const { - PERFETTO_DCHECK(pos < size_); - return data_[pos]; - } +// Use this for resources that are HANDLE on Windows. See comments in +// platform_handle.h +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +using ScopedPlatformHandle = ScopedResource; +#else +// On non-windows systems we alias ScopedPlatformHandle to ScopedFile because +// they are really the same. This is to allow assignments between the two in +// Linux-specific code paths that predate ScopedPlatformHandle. +static_assert(std::is_same::value, ""); +using ScopedPlatformHandle = ScopedFile; - size_t find(char c, size_t start_pos = 0) const { - for (size_t i = start_pos; i < size_; ++i) { - if (data_[i] == c) - return i; - } - return npos; - } +// DIR* does not exist on Windows. +using ScopedDir = ScopedResource; +#endif - size_t find(const StringView& str, size_t start_pos = 0) const { - if (start_pos > size()) - return npos; - auto it = std::search(begin() + start_pos, end(), str.begin(), str.end()); - size_t pos = static_cast(it - begin()); - return pos + str.size() <= size() ? pos : npos; - } +} // namespace base +} // namespace perfetto - size_t find(const char* str, size_t start_pos = 0) const { - return find(StringView(str), start_pos); - } +#endif // INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/sys_types.h +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - size_t rfind(char c) const { - for (size_t i = size_; i > 0; --i) { - if (data_[i - 1] == c) - return i - 1; - } - return npos; - } +#ifndef INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ +#define INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ - StringView substr(size_t pos, size_t count = npos) const { - if (pos >= size_) - return StringView("", 0); - size_t rcount = std::min(count, size_ - pos); - return StringView(data_ + pos, rcount); - } +// This headers deals with sys types commonly used in the codebase that are +// missing on Windows. - bool CaseInsensitiveEq(const StringView& other) const { - if (size() != other.size()) - return false; - if (size() == 0) - return true; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - return _strnicmp(data(), other.data(), size()) == 0; -#else - return strncasecmp(data(), other.data(), size()) == 0; -#endif - } +#include // IWYU pragma: export +#include - bool CaseInsensitiveOneOf(const std::vector& others) const { - for (const StringView& other : others) { - if (CaseInsensitiveEq(other)) { - return true; - } - } - return false; - } +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" - bool StartsWith(const StringView& other) const { - if (other.size() == 0) - return true; - if (size() == 0) - return false; - if (other.size() > size()) - return false; - return memcmp(data(), other.data(), other.size()) == 0; - } +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - bool EndsWith(const StringView& other) const { - if (other.size() == 0) - return true; - if (size() == 0) - return false; - if (other.size() > size()) - return false; - size_t off = size() - other.size(); - return memcmp(data() + off, other.data(), other.size()) == 0; - } +#ifdef __MINGW32__ - std::string ToStdString() const { - return size_ == 0 ? "" : std::string(data_, size_); - } +using uid_t = int; +using gid_t = int; - uint64_t Hash() const { - base::FnvHasher hasher; - hasher.Update(data_, size_); - return hasher.digest(); - } +#else - private: - const char* data_ = nullptr; - size_t size_ = 0; -}; +#if !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC) +// MinGW has these. clang-cl and MSVC, which use just the Windows SDK, don't. +using uid_t = int; +using gid_t = int; +using pid_t = int; +#endif // !GCC -inline bool operator==(const StringView& x, const StringView& y) { - if (x.size() != y.size()) - return false; - if (x.size() == 0) - return true; - return memcmp(x.data(), y.data(), x.size()) == 0; -} +using ssize_t = intptr_t; -inline bool operator!=(const StringView& x, const StringView& y) { - return !(x == y); -} +#endif // __MINGW32__ -inline bool operator<(const StringView& x, const StringView& y) { - auto size = std::min(x.size(), y.size()); - if (size == 0) - return x.size() < y.size(); - int result = memcmp(x.data(), y.data(), size); - return result < 0 || (result == 0 && x.size() < y.size()); -} +#endif // OS_WIN -inline bool operator>=(const StringView& x, const StringView& y) { - return !(x < y); -} +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +// From libcutils' android_filesystem_config.h +#ifndef AID_ROOT +#define AID_ROOT 0 +#endif +#ifndef AID_STATSD +#define AID_STATSD 1066 +#endif +#ifndef AID_SHELL +#define AID_SHELL 2000 +#endif +#endif -inline bool operator>(const StringView& x, const StringView& y) { - return y < x; -} +namespace perfetto { +namespace base { -inline bool operator<=(const StringView& x, const StringView& y) { - return !(y < x); -} +// The machine ID used in the tracing core. +using MachineID = uint32_t; +// The default value reserved for the host trace. +constexpr MachineID kDefaultMachineID = 0; + +constexpr uid_t kInvalidUid = static_cast(-1); +constexpr pid_t kInvalidPid = static_cast(-1); } // namespace base } // namespace perfetto -template <> -struct std::hash<::perfetto::base::StringView> { - size_t operator()(const ::perfetto::base::StringView& sv) const { - return static_cast(sv.Hash()); - } -}; - -#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ /* * Copyright (C) 2018 The Android Open Source Project * @@ -652,311 +628,245 @@ struct std::hash<::perfetto::base::StringView> { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ -#include -#include -#include +#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. +#include +#include -#include -#include +#include +#include #include #include -#include -#include #include -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" - -namespace perfetto { +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" +// gen_amalgamated expanded: #include "perfetto/base/status.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" + +namespace perfetto { namespace base { -inline char Lowercase(char c) { - return ('A' <= c && c <= 'Z') ? static_cast(c - ('A' - 'a')) : c; -} +class TaskRunner; -inline char Uppercase(char c) { - return ('a' <= c && c <= 'z') ? static_cast(c + ('A' - 'a')) : c; -} +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +using FileOpenMode = int; +inline constexpr char kDevNull[] = "NUL"; +inline constexpr char kFopenReadFlag[] = "r"; +#else +using FileOpenMode = mode_t; +inline constexpr char kDevNull[] = "/dev/null"; +inline constexpr char kFopenReadFlag[] = "re"; +#endif -// Returns true if the character is an ASCII whitespace character. -// This is a locale-independent version that only considers ASCII whitespace: -// space (0x20), tab (0x09), newline (0x0A), vertical tab (0x0B), -// form feed (0x0C), and carriage return (0x0D). -inline bool IsSpace(char c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || - c == '\r'; -} +constexpr FileOpenMode kFileModeInvalid = static_cast(-1); -inline std::optional CStringToUInt32(const char* s, int base = 10) { - char* endptr = nullptr; - auto value = static_cast(strtoul(s, &endptr, base)); - return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; -} +// Cross-platform variant of ReadFileDescriptor() that takes a PlatformHandle. +// On Windows normalizes ERROR_BROKEN_PIPE to EOF so behavior matches POSIX. +bool ReadPlatformHandle(PlatformHandle, std::string* out); -inline std::optional CStringToInt32(const char* s, int base = 10) { - char* endptr = nullptr; - auto value = static_cast(strtol(s, &endptr, base)); - return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; -} +// Reads from |fd|, appending what is currently available into |*out|. +// Returns: +// True: EOF reached (all writers of |fd| have closed their end). +// False: read error. On a non-blocking |fd| this includes EAGAIN (no data +// currently available but writers are still alive); callers can check +// IsAgain(errno) and retry on the next readability notification. +bool ReadFileDescriptor(int fd, std::string* out); -// Note: it saturates to 7fffffffffffffff if parsing a hex number >= 0x8000... -inline std::optional CStringToInt64(const char* s, int base = 10) { - char* endptr = nullptr; - auto value = static_cast(strtoll(s, &endptr, base)); - return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; -} +// Convenience wrapper around ReadFileDescriptor() that takes a FILE*. +bool ReadFileStream(FILE* f, std::string* out); -inline std::optional CStringToUInt64(const char* s, int base = 10) { - char* endptr = nullptr; - auto value = static_cast(strtoull(s, &endptr, base)); - return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; -} +// Opens |path| read-only and reads its contents into |*out|. +// Returns false if the file cannot be opened. +bool ReadFile(const std::string& path, std::string* out); -double StrToD(const char* nptr, char** endptr); +// A wrapper around read(2). It deals with Linux vs Windows includes. It also +// deals with handling EINTR. Has the same semantics of UNIX's read(2). +ssize_t Read(int fd, void* dst, size_t dst_size); -inline std::optional CStringToDouble(const char* s) { - char* endptr = nullptr; - double value = StrToD(s, &endptr); - std::optional result(std::nullopt); - if (*s != '\0' && *endptr == '\0') - result = value; - return result; -} +// Call write until all data is written or an error is detected. +// +// man 2 write: +// If a write() is interrupted by a signal handler before any bytes are +// written, then the call fails with the error EINTR; if it is +// interrupted after at least one byte has been written, the call +// succeeds, and returns the number of bytes written. +ssize_t WriteAll(int fd, const void* buf, size_t count); -inline std::optional StringToUInt32(const std::string& s, - int base = 10) { - return CStringToUInt32(s.c_str(), base); -} +// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, +// rewinds it to the beginning, copies the content, and restores the offset. +// |fd_in| can't be a pipe, socket of FIFO. +base::Status CopyFileContents(int fd_in, int fd_out); -inline std::optional StringToInt32(const std::string& s, - int base = 10) { - return CStringToInt32(s.c_str(), base); -} +ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); -inline std::optional StringToUInt64(const std::string& s, - int base = 10) { - return CStringToUInt64(s.c_str(), base); -} +ScopedFile OpenFile(const std::string& path, + int flags, + FileOpenMode = kFileModeInvalid); +ScopedFstream OpenFstream(const std::string& path, const std::string& mode); -inline std::optional StringToInt64(const std::string& s, - int base = 10) { - return CStringToInt64(s.c_str(), base); -} +// This is an alias for close(). It's to avoid leaking windows.h in headers. +// Exported because ScopedFile is used in the /include/ext API by Chromium +// component builds. +int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); -inline std::optional StringToDouble(const std::string& s) { - return CStringToDouble(s.c_str()); -} +bool FlushFile(int fd); -template -inline std::optional StringViewToNumber(const base::StringView& sv, - int base = 10) { - // std::from_chars() does not regonize the leading '+' character and only - // recognizes '-' so remove the '+' if it exists to avoid errors and match - // the behavior of the other string conversion utilities above. - size_t start_offset = !sv.empty() && sv.at(0) == '+' ? 1 : 0; - T value; - auto result = - std::from_chars(sv.begin() + start_offset, sv.end(), value, base); - if (result.ec == std::errc() && result.ptr == sv.end()) { - return value; - } else { - return std::nullopt; - } -} +// Duplicates an open descriptor. The duplicate is not inherited across exec. +ScopedFile DupFile(int fd); -inline std::optional StringViewToUInt32(const base::StringView& sv, - int base = 10) { - // std::from_chars() does not recognize the leading '-' character for - // unsigned conversions, but strtol does. To Mimic the behavior of strtol, - // attempt a signed converion if we see a leading '-', and then cast the - // result back to unsigned. - if (sv.size() > 0 && sv.at(0) == '-') { - return static_cast >( - StringViewToNumber(sv, base)); - } else { - return StringViewToNumber(sv, base); - } -} +// These queries follow symbolic links. False also means a path could not be +// inspected. IsSameFile compares file identities, so it detects hard links. +bool IsRegularFile(const std::string& path); +bool IsSameFile(const std::string& first, const std::string& second); -inline std::optional StringViewToInt32(const base::StringView& sv, - int base = 10) { - return StringViewToNumber(sv, base); -} +// Moves the file offset to |offset| bytes from the beginning of the file. +// Returns false if |offset| cannot be represented by the platform or the seek +// fails. +bool SeekFile(int fd, uint64_t offset); -inline std::optional StringViewToUInt64(const base::StringView& sv, - int base = 10) { - // std::from_chars() does not recognize the leading '-' character for - // unsigned conversions, but strtol does. To Mimic the behavior of strtol, - // attempt a signed converion if we see a leading '-', and then cast the - // result back to unsigned. - if (sv.size() > 0 && sv.at(0) == '-') { - return static_cast >( - StringViewToNumber(sv, base)); - } else { - return StringViewToNumber(sv, base); - } -} +// Changes the size of an open file to |size| bytes. +// Returns false if |size| cannot be represented by the platform or truncation +// fails. +bool TruncateFile(int fd, uint64_t size); -inline std::optional StringViewToInt64(const base::StringView& sv, - int base = 10) { - return StringViewToNumber(sv, base); -} +// Returns true if mkdir succeeds, false if it fails (see errno in that case). +// `mode` is the permission bits for the new directory; it is ignored on +// Windows. +bool Mkdir(const std::string& path, uint32_t mode = 0755); -// TODO: As of Clang 19.0 std::from_chars is unimplemented for type double -// despite being part of C++17 standard, and already being supported by GCC and -// MSVC. Enable this once we have double support in Clang. -// inline std::optional StringViewToDouble(const base::StringView& sv) { -// return StringViewToNumber(sv); -// } +// Calls rmdir() on UNIX, _rmdir() on Windows. +bool Rmdir(const std::string& path); -bool StartsWith(const std::string& str, const std::string& prefix); -bool EndsWith(const std::string& str, const std::string& suffix); -bool StartsWithAny(const std::string& str, - const std::vector& prefixes); -bool Contains(const std::string& haystack, const std::string& needle); -bool Contains(const std::string& haystack, char needle); -bool Contains(const std::vector& haystack, - const std::string& needle); -size_t Find(const StringView& needle, const StringView& haystack); -bool CaseInsensitiveEqual(const std::string& first, const std::string& second); -std::string Join(const std::vector& parts, - const std::string& delim); -std::vector SplitString(const std::string& text, - const std::string& delimiter); -std::string StripPrefix(const std::string& str, const std::string& prefix); -std::string StripSuffix(const std::string& str, const std::string& suffix); -std::string_view TrimWhitespace(std::string_view str); -std::string TrimWhitespace(const std::string& str); -std::string_view TrimWhitespace(const char* str); -std::string ToLower(const std::string& str); -std::string ToUpper(const std::string& str); -std::string StripChars(const std::string& str, - const std::string& chars, - char replacement); -std::string ToHex(const char* data, size_t size); -inline std::string ToHex(const std::string& s) { - return ToHex(s.c_str(), s.size()); -} -std::string IntToHexString(uint32_t number); -std::string Uint64ToHexString(uint64_t number); -std::string Uint64ToHexStringNoPrefix(uint64_t number); -std::string ReplaceAll(std::string str, - const std::string& to_replace, - const std::string& replacement); +// Removes a file: unlink() on UNIX, _unlink() on Windows. Takes a const char* +// and is async-signal-safe on POSIX, so it's callable from a signal handler. +bool Unlink(const char* path); -// Checks if all characters in the input string view `str` are ASCII. -// -// If so, the function returns true and `output` is not modified. -// If `str` contains non-ASCII characters, the function returns false, -// removes invalid UTF-8 characters from `str`, and stores the result in -// `output`. -bool CheckAsciiAndRemoveInvalidUTF8(base::StringView str, std::string& output); +// Wrapper around access(path, F_OK). +bool FileExists(const std::string& path); -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -bool WideToUTF8(const std::wstring& source, std::string& output); -bool UTF8ToWide(const std::string& source, std::wstring& output); -#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +// Returns true if the path exists and is a directory. +bool DirectoryExists(const std::string& path); -// A BSD-style strlcpy without the return value. -// Copies at most |dst_size|-1 characters. Unlike strncpy, it always \0 -// terminates |dst|, as long as |dst_size| is not 0. -// Unlike strncpy and like strlcpy it does not zero-pad the rest of |dst|. -// Returns nothing. The BSD strlcpy returns the size of |src|, which might -// be > |dst_size|. Anecdotal experience suggests people assume the return value -// is the number of bytes written in |dst|. That assumption can lead to -// dangerous bugs. -// In order to avoid being subtly uncompliant with strlcpy AND avoid misuse, -// the choice here is to return nothing. -inline void StringCopy(char* dst, const char* src, size_t dst_size) { - for (size_t i = 0; i < dst_size; ++i) { - if ((dst[i] = src[i]) == '\0') { - return; // We hit and copied the null terminator. - } - } +// Gets the extension for a filename. If the file has two extensions, returns +// only the last one (foo.pb.gz => .gz). Returns empty string if there is no +// extension. +std::string GetFileExtension(const std::string& filename); - // We were left off at dst_size. We over copied 1 byte. Null terminate. - if (PERFETTO_LIKELY(dst_size > 0)) - dst[dst_size - 1] = 0; -} +// Returns the basename component of a path (the final component after the last +// directory separator). Behaves like man 2 basename, but works with both '/' +// and '\' separators for cross-platform compatibility. +// Examples: +// Basename("/usr/bin/ls") => "ls" +// Basename("/usr/bin/") => "bin" +// Basename("/") => "/" +// Basename("foo") => "foo" +// Basename("") => "." +// Basename("C:\\Windows\\System32") => "System32" +std::string Basename(const std::string& path); -// Like snprintf() but returns the number of chars *actually* written (without -// counting the null terminator) NOT "the number of chars which would have been -// written to the final string if enough space had been available". -// This should be used in almost all cases when the caller uses the return value -// of snprintf(). If the return value is not used, there is no benefit in using -// this wrapper, as this just calls snprintf() and mangles the return value. -// It always null-terminates |dst| (even in case of errors), unless -// |dst_size| == 0. +// Returns the directory component of a path (everything up to but not +// including the final component). Behaves like man 2 dirname, but works with +// both '/' and '\' separators for cross-platform compatibility. // Examples: -// SprintfTrunc(x, 4, "123whatever"): returns 3 and writes "123\0". -// SprintfTrunc(x, 4, "123"): returns 3 and writes "123\0". -// SprintfTrunc(x, 3, "123"): returns 2 and writes "12\0". -// SprintfTrunc(x, 2, "123"): returns 1 and writes "1\0". -// SprintfTrunc(x, 1, "123"): returns 0 and writes "\0". -// SprintfTrunc(x, 0, "123"): returns 0 and writes nothing. -// NOTE: This means that the caller has no way to tell when truncation happens -// vs the edge case of *just* fitting in the buffer. -size_t SprintfTrunc(char* dst, size_t dst_size, const char* fmt, ...) - PERFETTO_PRINTF_FORMAT(3, 4); +// Dirname("/usr/bin/ls") => "/usr/bin" +// Dirname("/usr/bin") => "/usr" +// Dirname("/") => "/" +// Dirname("foo") => "." +// Dirname("") => "." +// Dirname("C:\\Windows\\System32") => "C:\\Windows" +std::string Dirname(const std::string& path); -// Line number starts from 1 -struct LineWithOffset { - base::StringView line; - uint32_t line_offset; - uint32_t line_num; -}; +// Returns the length of the leading root component of a path: "/" on UNIX, +// "C:\" or "\\" (UNC) on Windows. Returns 0 if the path is relative. Like +// Basename() and Dirname(), both '/' and '\' are recognized on all platforms. +// Stripping the returned prefix turns the path into a relative one. +// Examples: +// PathRootPrefixLength("/usr/bin/ls") => 1 +// PathRootPrefixLength("//usr/bin") => 2 +// PathRootPrefixLength("C:\\Windows") => 3 +// PathRootPrefixLength("C:foo") => 0 (relative to the current dir of drive +// C:) PathRootPrefixLength("foo/bar") => 0 +size_t PathRootPrefixLength(const std::string& path); -// For given string and offset Pfinds a line with character for -// which offset points, what number is this line (starts from 1), and the offset -// inside this line. returns std::nullopt if the offset points to -// line break character or exceeds string length. -std::optional FindLineWithOffset(base::StringView str, - uint32_t offset); +// Returns true if the path starts with a root component. See +// PathRootPrefixLength(). +bool IsAbsolutePath(const std::string& path); -// A helper class to facilitate construction and usage of write-once stack -// strings. -// Example usage: -// StackString<32> x("format %d %s", 42, string_arg); -// TakeString(x.c_str() | x.string_view() | x.ToStdString()); -// Rather than char x[32] + sprintf. -// Advantages: -// - Avoids useless zero-fills caused by people doing `char buf[32] {}` (mainly -// by fearing unknown snprintf failure modes). -// - Makes the code more robust in case of snprintf truncations (len() and -// string_view() will return the truncated length, unlike snprintf). -template -class StackString { +// Puts the path to all files under |dir_path| in |output|, recursively walking +// subdirectories. File paths are relative to |dir_path|. Only files are +// included, not directories. Path separator is always '/', even on windows (not +// '\'). +base::Status ListFilesRecursive(const std::string& dir_path, + std::vector& output); + +// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names +// are relative to |dir_path| and do not include the path separator. Returns +// only directories, not files. Works on both Unix and Windows. +base::Status ListDirectories(const std::string& dir_path, + std::vector& output); + +// Sets |path|'s owner group to |group_name| and permission mode bits to +// |mode_bits|. +base::Status SetFilePermissions(const std::string& path, + const std::string& group_name, + const std::string& mode_bits); + +// Returns the size of the file located at |path|, or nullopt in case of error. +std::optional GetFileSize(const std::string& path); + +// Returns the size of the open file |fd|, or nullopt in case of error. +std::optional GetFileSize(PlatformHandle fd); + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +// On Windows PlatformHandle is a HANDLE, not a file descriptor. This overload +// accepts the CRT file descriptors returned by base::OpenFile(). On other +// platforms PlatformHandle is an int, so the overload above already covers it. +std::optional GetFileSize(int fd); +#endif + +// This class uses inotify (on Linux/Android) to watch for the creation of +// files in the filesystem. When the specified file is created, it triggers a +// callback function. +// Destroying the returned unique_ptr will automatically unregister the watch. +// +// Note: This only works with filesystem paths (not abstract sockets or other +// special file types). +// It's only supported on Linux and Android, it's a no-op (returns nullptr) on +// other platforms. +// +// Usage: +// auto watch = LinuxFileWatch::WatchFileCreation( +// task_runner, "/tmp/my_file", []() { +// // Called when /tmp/my_file is created +// }); +class LinuxFileWatch { public: - explicit PERFETTO_PRINTF_FORMAT(/* 1=this */ 2, 3) - StackString(const char* fmt, ...) { - buf_[0] = '\0'; - va_list args; - va_start(args, fmt); - int res = vsnprintf(buf_, sizeof(buf_), fmt, args); - va_end(args); - buf_[sizeof(buf_) - 1] = '\0'; - len_ = res < 0 ? 0 : std::min(static_cast(res), sizeof(buf_) - 1); - } + // Creates a watcher for file creation. Returns nullptr if the path is not a + // valid filesystem path or if the platform doesn't support inotify. The + // callback will be invoked on the provided TaskRunner when the file is + // created. + static std::unique_ptr WatchFileCreation( + TaskRunner*, + const char* path, + std::function callback); - StringView string_view() const { return StringView(buf_, len_); } - std::string ToStdString() const { return std::string(buf_, len_); } - const char* c_str() const { return buf_; } - size_t len() const { return len_; } - char* mutable_data() { return buf_; } + virtual ~LinuxFileWatch(); - private: - char buf_[N]; - size_t len_ = 0; // Does not include the \0. + protected: + LinuxFileWatch() = default; }; } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/string_splitter.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -971,185 +881,239 @@ class StackString { * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/ext/base/android_utils.h" - -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ #include -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -#include -#endif - -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/base/time.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" +namespace perfetto { +namespace base { -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) -#include -#include -#endif +// C++ version of strtok(). Splits a string without making copies or any heap +// allocations. Destructs the original string passed in input. +// Supports the special case of using \0 as a delimiter. +// The token returned in output are valid as long as the input string is valid. +class StringSplitter { + public: + // Whether an empty string (two delimiters side-to-side) is a valid token. + enum class EmptyTokenMode { + DISALLOW_EMPTY_TOKENS, + ALLOW_EMPTY_TOKENS, -#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -#include -#endif + DEFAULT = DISALLOW_EMPTY_TOKENS, + }; -namespace perfetto { -namespace base { + // Can take ownership of the string if passed via std::move(), e.g.: + // StringSplitter(std::move(str), '\n'); + StringSplitter(std::string, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + // Splits a C-string. The input string will be forcefully null-terminated (so + // str[size - 1] should be == '\0' or the last char will be truncated). + StringSplitter(char* str, + size_t size, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); -std::string GetAndroidProp(const char* name) { - std::string ret; -#if __ANDROID_API__ >= 26 - const prop_info* pi = __system_property_find(name); - if (!pi) { - return ret; - } - __system_property_read_callback( - pi, - [](void* dst_void, const char*, const char* value, uint32_t) { - std::string& dst = *static_cast(dst_void); - dst = value; - }, - &ret); -#else // __ANDROID_API__ < 26 - char value_buf[PROP_VALUE_MAX]; - int len = __system_property_get(name, value_buf); - if (len > 0 && static_cast(len) < sizeof(value_buf)) { - ret = std::string(value_buf, static_cast(len)); - } -#endif - return ret; -} + // Splits the current token from an outer StringSplitter instance. This is to + // chain splitters as follows: + // for (base::StringSplitter lines(x, '\n'); ss.Next();) + // for (base::StringSplitter words(&lines, ' '); words.Next();) + StringSplitter(StringSplitter*, + char delimiter, + EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); -#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + // Returns true if a token is found (in which case it will be stored in + // cur_token()), false if no more tokens are found. + bool Next(); -Utsname GetUtsname() { - Utsname utsname_info; -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) - struct utsname uname_info; - if (uname(&uname_info) == 0) { - utsname_info.sysname = uname_info.sysname; - utsname_info.version = uname_info.version; - utsname_info.machine = uname_info.machine; - utsname_info.release = uname_info.release; - } else { - PERFETTO_ELOG("Unable to read Utsname information"); - } -#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) - return utsname_info; -} + // Returns the next token if found (in which case it will be stored in + // cur_token()), nullptr if no more tokens are found. + char* NextToken() { return Next() ? cur_token() : nullptr; } -SystemInfo GetSystemInfo() { - SystemInfo info; + // Returns the current token iff last call to Next() returned true. In this + // case it guarantees that the returned string is always null terminated. + // In all other cases (before the 1st call to Next() and after Next() returns + // false) returns nullptr. + char* cur_token() { return cur_; } - info.timezone_off_mins = GetTimezoneOffsetMins(); + // Returns the length of the current token (excluding the null terminator). + size_t cur_token_size() const { return cur_size_; } -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) + // Return the untokenized remainder of the input string that occurs after the + // current token. + char* remainder() { return next_; } - info.utsname_info = GetUtsname(); - info.page_size = static_cast(sysconf(_SC_PAGESIZE)); - info.num_cpus = static_cast(sysconf(_SC_NPROCESSORS_CONF)); + // Returns the size of the untokenized input + size_t remainder_size() { return static_cast(end_ - next_); } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ - PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - // Use the Linux-specific sysinfo() system call on Linux and Android. - // https://man7.org/linux/man-pages/man2/sysinfo.2.html - struct sysinfo sys_info; - if (sysinfo(&sys_info) == 0) { - info.system_ram_bytes = - static_cast(sys_info.totalram) * sys_info.mem_unit; - } -#else - // POSIX Fallback (macOS, BSD, etc.): Use sysconf() to get physical pages. - long pages = sysconf(_SC_PHYS_PAGES); - if (pages > 0 && info.page_size.has_value()) { - info.system_ram_bytes = static_cast(pages) * (*info.page_size); - } -#endif -#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - info.android_build_fingerprint = GetAndroidProp("ro.build.fingerprint"); - if (info.android_build_fingerprint.empty()) { - PERFETTO_ELOG("Unable to read ro.build.fingerprint"); + private: + StringSplitter(const StringSplitter&) = delete; + StringSplitter& operator=(const StringSplitter&) = delete; + void Initialize(char* str, size_t size); + + std::string str_; + char* cur_; + size_t cur_size_; + char* next_; + char* end_; // STL-style, points one past the last char. + const char delimiter_; + const EmptyTokenMode empty_token_mode_; +}; + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/string_utils.h +// gen_amalgamated begin header: include/perfetto/ext/base/string_view.h +// gen_amalgamated begin header: include/perfetto/ext/base/fnv_hash.h +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ +#define INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ + +#include +#include +#include +#include +#include + +namespace perfetto::base { + +// A helper class which computes a 64-bit hash of the input data. +// The algorithm used is FNV-1a as it is fast and easy to implement and has +// relatively few collisions. +// WARNING: This hash function should not be used for any cryptographic purpose. +class FnvHasher { + public: + // Creates an empty hash object + constexpr FnvHasher() = default; + + // Hashes a numeric value. + template < + typename T, + typename std::enable_if::value, bool>::type = true> + void Update(T data) { + Update(reinterpret_cast(&data), sizeof(data)); } - info.android_device_manufacturer = GetAndroidProp("ro.product.manufacturer"); - if (info.android_device_manufacturer.empty()) { - PERFETTO_ELOG("Unable to read ro.product.manufacturer"); + constexpr void Update(char c) { return Update(&c, 1); } + + // Using the loop instead of "Update(str, strlen(str))" to avoid looping twice + constexpr void Update(const char* str) { + for (const auto* p = str; *p; ++p) + Update(*p); } - std::string sdk_str_value = GetAndroidProp("ro.build.version.sdk"); - info.android_sdk_version = StringToUInt64(sdk_str_value); - if (!info.android_sdk_version.has_value()) { - PERFETTO_ELOG("Unable to read ro.build.version.sdk"); + // Hashes a byte array. + constexpr void Update(const char* data, size_t size) { + for (size_t i = 0; i < size; i++) { + result_ ^= static_cast(data[i]); + // Note: Arithmetic overflow of unsigned integers is well defined in C++ + // standard unlike signed integers. + // https://stackoverflow.com/a/41280273 + result_ *= kFnv1a64Prime; + } } - info.android_soc_model = GetAndroidProp("ro.soc.model"); - if (info.android_soc_model.empty()) { - PERFETTO_ELOG("Unable to read ro.soc.model"); + // Allow hashing anything that has `data` and `size` and has the kHashable + // trait (e.g., base::StringView). + template > + constexpr void Update(const T& t) { + if constexpr (std::is_member_function_pointer_v) { + Update(t.data(), t.size()); + } else { + Update(t.data, t.size); + } } - // guest_soc model is not always present - info.android_guest_soc_model = GetAndroidProp("ro.boot.guest_soc.model"); + constexpr void Update(std::string_view s) { Update(s.data(), s.size()); } - info.android_hardware_revision = GetAndroidProp("ro.boot.hardware.revision"); - if (info.android_hardware_revision.empty()) { - PERFETTO_ELOG("Unable to read ro.boot.hardware.revision"); - } + constexpr uint64_t digest() const { return result_; } - info.android_storage_model = GetAndroidProp("ro.boot.hardware.ufs"); - if (info.android_storage_model.empty()) { - PERFETTO_ELOG("Unable to read ro.boot.hardware.ufs"); + // Usage: + // uint64_t hashed_value = + // FnvHasher::Combine(33, false, "ABC", 458L, 3u, 'x'); + template + static constexpr uint64_t Combine(Ts&&... args) { + FnvHasher hasher; + hasher.UpdateAll(std::forward(args)...); + return hasher.digest(); } - info.android_ram_model = GetAndroidProp("ro.boot.hardware.ddr"); - if (info.android_ram_model.empty()) { - PERFETTO_ELOG("Unable to read ro.boot.hardware.ddr"); + // Creates a hasher with `args` already hashed. + // + // Usage: + // FnvHasher partial = FnvHasher::CreatePartial(33, false, "ABC", 458L); + template + static constexpr FnvHasher CreatePartial(Ts&&... args) { + FnvHasher hasher; + hasher.UpdateAll(std::forward(args)...); + return hasher; } - info.android_serial_console = GetAndroidProp("init.svc.console"); - if (info.android_serial_console.empty()) { - PERFETTO_ELOG("Unable to read init.svc.console"); + // `hasher.UpdateAll(33, false, "ABC")` is shorthand for: + // `hasher.Update(33); hasher.Update(false); hasher.Update("ABC");` + constexpr void UpdateAll() {} + + template + constexpr void UpdateAll(T&& arg, Ts&&... args) { + Update(arg); + UpdateAll(std::forward(args)...); } -#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - return info; -} + private: + static constexpr uint64_t kFnv1a64OffsetBasis = 0xcbf29ce484222325; + static constexpr uint64_t kFnv1a64Prime = 0x100000001b3; -std::string GetPerfettoMachineName() { - const char* env_name = getenv("PERFETTO_MACHINE_NAME"); - if (env_name) { - return env_name; + uint64_t result_ = kFnv1a64OffsetBasis; +}; + +// base::FnvHash uses base::FnvHasher for integer values and falls back to +// std::hash for other types. This is needed as std::hash for integers is just +// the identity function and Perfetto uses open-addressing hash table, which are +// very sensitive to hash quality and are known to degrade in performance +// when using std::hash. +template +struct FnvHash { + // Version for ints, using base::FnvHasher. + template + auto operator()(const U& x) -> + typename std::enable_if::value, size_t>::type + const { + return FnvHasher::Combine(x); } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - std::string name = base::GetAndroidProp("traced.machine_name"); - if (name.empty()) { - name = GetUtsname().sysname; + + // Version for non-ints, falling back to std::hash. + template + auto operator()(const U& x) -> + typename std::enable_if::value, size_t>::type + const { + return std::hash()(x); } - return name; -#else - return GetUtsname().sysname; -#endif -} +}; -} // namespace base -} // namespace perfetto -// gen_amalgamated begin source: src/base/base64.cc -// gen_amalgamated begin header: include/perfetto/ext/base/base64.h -// gen_amalgamated begin header: include/perfetto/ext/base/utils.h -// gen_amalgamated begin header: include/perfetto/ext/base/sys_types.h +} // namespace perfetto::base + +#endif // INCLUDE_PERFETTO_EXT_BASE_FNV_HASH_H_ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1164,73 +1128,205 @@ std::string GetPerfettoMachineName() { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ -#define INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ +#define INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ -// This headers deals with sys types commonly used in the codebase that are -// missing on Windows. +#include -#include // IWYU pragma: export -#include +#include +#include +#include // gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/fnv_hash.h" -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +namespace perfetto { +namespace base { -#ifdef __MINGW32__ +// A string-like object that refers to a non-owned piece of memory. +// Strings are internally NOT null terminated. +class StringView { + public: + // Allow hashing with base::FnvHash. + static constexpr bool kHashable = true; + static constexpr size_t npos = static_cast(-1); -using uid_t = int; -using gid_t = int; + StringView() : data_(nullptr), size_(0) {} + StringView(const StringView&) = default; + StringView& operator=(const StringView&) = default; + StringView(const char* data, size_t size) : data_(data), size_(size) { + PERFETTO_DCHECK(size == 0 || data != nullptr); + } -#else + // Allow implicit conversion from any class that has a |data| and |size| field + // and has the kConvertibleToStringView trait (e.g., protozero::ConstChars). + template > + StringView(const T& x) : StringView(x.data, x.size) { + PERFETTO_DCHECK(x.size == 0 || x.data != nullptr); + } -#if !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_GCC) -// MinGW has these. clang-cl and MSVC, which use just the Windows SDK, don't. -using uid_t = int; -using gid_t = int; -using pid_t = int; -#endif // !GCC + // Creates a StringView from a null-terminated C string. + // Deliberately not "explicit". + StringView(const char* cstr) : data_(cstr), size_(strlen(cstr)) { + PERFETTO_DCHECK(cstr != nullptr); + } -#if defined(_WIN64) -using ssize_t = int64_t; -#else -using ssize_t = long; -#endif // _WIN64 + StringView(std::string_view str) : data_(str.data()), size_(str.size()) {} -#endif // __MINGW32__ + // This instead has to be explicit, as creating a StringView out of a + // std::string can be subtle. + explicit StringView(const std::string& str) + : data_(str.data()), size_(str.size()) {} -#endif // OS_WIN + bool empty() const { return size_ == 0; } + size_t size() const { return size_; } + const char* data() const { return data_; } + const char* begin() const { return data_; } + const char* end() const { return data_ + size_; } -#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -// From libcutils' android_filesystem_config.h -#ifndef AID_ROOT -#define AID_ROOT 0 -#endif -#ifndef AID_STATSD -#define AID_STATSD 1066 -#endif -#ifndef AID_SHELL -#define AID_SHELL 2000 -#endif + char at(size_t pos) const { + PERFETTO_DCHECK(pos < size_); + return data_[pos]; + } + + size_t find(char c, size_t start_pos = 0) const { + for (size_t i = start_pos; i < size_; ++i) { + if (data_[i] == c) + return i; + } + return npos; + } + + size_t find(const StringView& str, size_t start_pos = 0) const { + if (start_pos > size()) + return npos; + auto it = std::search(begin() + start_pos, end(), str.begin(), str.end()); + size_t pos = static_cast(it - begin()); + return pos + str.size() <= size() ? pos : npos; + } + + size_t find(const char* str, size_t start_pos = 0) const { + return find(StringView(str), start_pos); + } + + size_t rfind(char c) const { + for (size_t i = size_; i > 0; --i) { + if (data_[i - 1] == c) + return i - 1; + } + return npos; + } + + StringView substr(size_t pos, size_t count = npos) const { + if (pos >= size_) + return StringView("", 0); + size_t rcount = std::min(count, size_ - pos); + return StringView(data_ + pos, rcount); + } + + bool CaseInsensitiveEq(const StringView& other) const { + if (size() != other.size()) + return false; + if (size() == 0) + return true; +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + return _strnicmp(data(), other.data(), size()) == 0; +#else + return strncasecmp(data(), other.data(), size()) == 0; #endif + } -namespace perfetto { -namespace base { + bool CaseInsensitiveOneOf(const std::vector& others) const { + for (const StringView& other : others) { + if (CaseInsensitiveEq(other)) { + return true; + } + } + return false; + } -// The machine ID used in the tracing core. -using MachineID = uint32_t; -// The default value reserved for the host trace. -constexpr MachineID kDefaultMachineID = 0; + bool StartsWith(const StringView& other) const { + if (other.size() == 0) + return true; + if (size() == 0) + return false; + if (other.size() > size()) + return false; + return memcmp(data(), other.data(), other.size()) == 0; + } -constexpr uid_t kInvalidUid = static_cast(-1); -constexpr pid_t kInvalidPid = static_cast(-1); + bool EndsWith(const StringView& other) const { + if (other.size() == 0) + return true; + if (size() == 0) + return false; + if (other.size() > size()) + return false; + size_t off = size() - other.size(); + return memcmp(data() + off, other.data(), other.size()) == 0; + } + + std::string ToStdString() const { + return size_ == 0 ? "" : std::string(data_, size_); + } + + uint64_t Hash() const { + base::FnvHasher hasher; + hasher.Update(data_, size_); + return hasher.digest(); + } + + private: + const char* data_ = nullptr; + size_t size_ = 0; +}; + +inline bool operator==(const StringView& x, const StringView& y) { + if (x.size() != y.size()) + return false; + if (x.size() == 0) + return true; + return memcmp(x.data(), y.data(), x.size()) == 0; +} + +inline bool operator!=(const StringView& x, const StringView& y) { + return !(x == y); +} + +inline bool operator<(const StringView& x, const StringView& y) { + auto size = std::min(x.size(), y.size()); + if (size == 0) + return x.size() < y.size(); + int result = memcmp(x.data(), y.data(), size); + return result < 0 || (result == 0 && x.size() < y.size()); +} + +inline bool operator>=(const StringView& x, const StringView& y) { + return !(x < y); +} + +inline bool operator>(const StringView& x, const StringView& y) { + return y < x; +} + +inline bool operator<=(const StringView& x, const StringView& y) { + return !(y < x); +} } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_SYS_TYPES_H_ +template <> +struct std::hash<::perfetto::base::StringView> { + size_t operator()(const ::perfetto::base::StringView& sv) const { + return static_cast(sv.Hash()); + } +}; + +#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_VIEW_H_ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1245,275 +1341,312 @@ constexpr pid_t kInvalidPid = static_cast(-1); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ -#include -#include -#include -#include +#include #include +#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -// Even if Windows has errno.h, the all syscall-restart behavior does not apply. -// Trying to handle EINTR can cause more harm than good if errno is left stale. -// Chromium does the same. -#define PERFETTO_EINTR(x) (x) -#else -#define PERFETTO_EINTR(x) \ - ([&] { \ - decltype(x) eintr_wrapper_result; \ - do { \ - eintr_wrapper_result = (x); \ - } while (eintr_wrapper_result == -1 && errno == EINTR); \ - return eintr_wrapper_result; \ - }()) -#endif +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" namespace perfetto { namespace base { -namespace internal { -extern std::atomic g_cached_page_size; -uint32_t GetSysPageSizeSlowpath(); -} // namespace internal +inline char Lowercase(char c) { + return ('A' <= c && c <= 'Z') ? static_cast(c - ('A' - 'a')) : c; +} -// Returns the system's page size. Use this when dealing with mmap, madvise and -// similar mm-related syscalls. -// This function might be called in hot paths. Avoid calling getpagesize() all -// the times, in many implementations getpagesize() calls sysconf() which is -// not cheap. -inline uint32_t GetSysPageSize() { - const uint32_t page_size = - internal::g_cached_page_size.load(std::memory_order_relaxed); - return page_size != 0 ? page_size : internal::GetSysPageSizeSlowpath(); +inline char Uppercase(char c) { + return ('a' <= c && c <= 'z') ? static_cast(c + ('A' - 'a')) : c; } -template -constexpr size_t ArraySize(const T (&)[TSize]) { - return TSize; +// Returns true if the character is an ASCII whitespace character. +// This is a locale-independent version that only considers ASCII whitespace: +// space (0x20), tab (0x09), newline (0x0A), vertical tab (0x0B), +// form feed (0x0C), and carriage return (0x0D). +inline bool IsSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || + c == '\r'; } -// Adds `a` and `b`, clamping to INT64_MIN / INT64_MAX on overflow instead of -// wrapping (which is UB and can flip the sign of the result). -inline int64_t SaturatingAdd(int64_t a, int64_t b) { - constexpr int64_t kMax = std::numeric_limits::max(); - constexpr int64_t kMin = std::numeric_limits::min(); -#if defined(__clang__) || defined(__GNUC__) - int64_t result; - if (PERFETTO_UNLIKELY(__builtin_add_overflow(a, b, &result))) - return a < 0 ? kMin : kMax; - return result; -#else - if (b > 0 && a > kMax - b) - return kMax; - if (b < 0 && a < kMin - b) - return kMin; - return a + b; -#endif +inline std::optional CStringToUInt32(const char* s, int base = 10) { + char* endptr = nullptr; + auto value = static_cast(strtoul(s, &endptr, base)); + return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; } -// Multiplies `a` by `b`, clamping to INT64_MIN / INT64_MAX on overflow instead -// of wrapping (which is UB and can flip the sign of the result). -inline int64_t SaturatingMultiply(int64_t a, int64_t b) { - constexpr int64_t kMax = std::numeric_limits::max(); - constexpr int64_t kMin = std::numeric_limits::min(); -#if defined(__clang__) || defined(__GNUC__) - int64_t result; - if (PERFETTO_UNLIKELY(__builtin_mul_overflow(a, b, &result))) { - // On overflow the true product's sign is the XOR of the operands' signs. - // Neither operand can be zero here (0 never overflows). - return ((a < 0) == (b < 0)) ? kMax : kMin; - } +inline std::optional CStringToInt32(const char* s, int base = 10) { + char* endptr = nullptr; + auto value = static_cast(strtol(s, &endptr, base)); + return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; +} + +// Note: it saturates to 7fffffffffffffff if parsing a hex number >= 0x8000... +inline std::optional CStringToInt64(const char* s, int base = 10) { + char* endptr = nullptr; + auto value = static_cast(strtoll(s, &endptr, base)); + return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; +} + +inline std::optional CStringToUInt64(const char* s, int base = 10) { + char* endptr = nullptr; + auto value = static_cast(strtoull(s, &endptr, base)); + return (*s && !*endptr) ? std::make_optional(value) : std::nullopt; +} + +double StrToD(const char* nptr, char** endptr); + +inline std::optional CStringToDouble(const char* s) { + char* endptr = nullptr; + double value = StrToD(s, &endptr); + std::optional result(std::nullopt); + if (*s != '\0' && *endptr == '\0') + result = value; return result; -#else - // Portable fallback (e.g. MSVC cl.exe): detect overflow *before* multiplying - // by dividing a limit by one operand, which never itself overflows. - if (a == 0 || b == 0) - return 0; - if ((a < 0) == (b < 0)) { // Same sign: product is positive. - if (a > 0 ? (a > kMax / b) : (a < kMax / b)) - return kMax; - } else { // Opposite signs: product is negative. - if (a > 0 ? (b < kMin / a) : (a < kMin / b)) - return kMin; - } - return a * b; -#endif } -// Function object which invokes 'free' on its parameter, which must be -// a pointer. Can be used to store malloc-allocated pointers in std::unique_ptr: -// -// std::unique_ptr foo_ptr( -// static_cast(malloc(sizeof(int)))); -struct FreeDeleter { - inline void operator()(void* ptr) const { free(ptr); } -}; +inline std::optional StringToUInt32(const std::string& s, + int base = 10) { + return CStringToUInt32(s.c_str(), base); +} -template -constexpr T AssumeLittleEndian(T value) { -#if !PERFETTO_IS_LITTLE_ENDIAN() - static_assert(false, "Unimplemented on big-endian archs"); -#endif - return value; +inline std::optional StringToInt32(const std::string& s, + int base = 10) { + return CStringToInt32(s.c_str(), base); } -// Round up |size| to a multiple of |alignment| (must be a power of two). -inline constexpr size_t AlignUp(size_t size, size_t alignment) { - return (size + alignment - 1) & ~(alignment - 1); +inline std::optional StringToUInt64(const std::string& s, + int base = 10) { + return CStringToUInt64(s.c_str(), base); } -// Round down |size| to a multiple of |alignment| (must be a power of two). -inline constexpr size_t AlignDown(size_t size, size_t alignment) { - return size & ~(alignment - 1); +inline std::optional StringToInt64(const std::string& s, + int base = 10) { + return CStringToInt64(s.c_str(), base); } -template -inline constexpr bool IsPowerOfTwo(T x) { - static_assert(std::is_unsigned_v && std::is_integral_v, - "T must be an unsigned integer"); - return x != 0 && (x & (x - 1)) == 0; +inline std::optional StringToDouble(const std::string& s) { + return CStringToDouble(s.c_str()); } -// Returns the smallest power of two greater than or equal to |x|. Returns zero -// for zero or when the result is not representable by T. template -inline constexpr T RoundUpToPowerOfTwo(T x) { - static_assert(std::is_unsigned_v && std::is_integral_v, - "T must be an unsigned integer"); - if (x == 0) { - return 0; - } - --x; - for (size_t shift = 1; shift < sizeof(T) * 8; shift *= 2) { - x |= x >> shift; +inline std::optional StringViewToNumber(const base::StringView& sv, + int base = 10) { + // std::from_chars() does not regonize the leading '+' character and only + // recognizes '-' so remove the '+' if it exists to avoid errors and match + // the behavior of the other string conversion utilities above. + size_t start_offset = !sv.empty() && sv.at(0) == '+' ? 1 : 0; + T value; + auto result = + std::from_chars(sv.begin() + start_offset, sv.end(), value, base); + if (result.ec == std::errc() && result.ptr == sv.end()) { + return value; + } else { + return std::nullopt; } - return ++x; } -// TODO(primiano): clean this up and move all existing usages to the constexpr -// version above. -template -constexpr size_t AlignUp(size_t size) { - static_assert(IsPowerOfTwo(alignment), "alignment must be a pow2"); - return AlignUp(size, alignment); +inline std::optional StringViewToUInt32(const base::StringView& sv, + int base = 10) { + // std::from_chars() does not recognize the leading '-' character for + // unsigned conversions, but strtol does. To Mimic the behavior of strtol, + // attempt a signed converion if we see a leading '-', and then cast the + // result back to unsigned. + if (sv.size() > 0 && sv.at(0) == '-') { + return static_cast >( + StringViewToNumber(sv, base)); + } else { + return StringViewToNumber(sv, base); + } } -inline bool IsAgain(int err) { - return err == EAGAIN || err == EWOULDBLOCK; +inline std::optional StringViewToInt32(const base::StringView& sv, + int base = 10) { + return StringViewToNumber(sv, base); } -// setenv(2)-equivalent. Deals with Windows vs Posix discrepancies. -void SetEnv(const std::string& key, const std::string& value); - -// unsetenv(2)-equivalent. Deals with Windows vs Posix discrepancies. -void UnsetEnv(const std::string& key); - -// Returns true if |fd| is connected to an interactive terminal (TTY). Deals -// with Windows vs Posix discrepancies (isatty() vs _isatty()). -bool IsTty(int fd); +inline std::optional StringViewToUInt64(const base::StringView& sv, + int base = 10) { + // std::from_chars() does not recognize the leading '-' character for + // unsigned conversions, but strtol does. To Mimic the behavior of strtol, + // attempt a signed converion if we see a leading '-', and then cast the + // result back to unsigned. + if (sv.size() > 0 && sv.at(0) == '-') { + return static_cast >( + StringViewToNumber(sv, base)); + } else { + return StringViewToNumber(sv, base); + } +} -// Convenience overload for C stdio streams (e.g. stdin/stdout/stderr). -bool IsTty(FILE* stream); +inline std::optional StringViewToInt64(const base::StringView& sv, + int base = 10) { + return StringViewToNumber(sv, base); +} -// Calls mallopt(M_PURGE, 0) on Android. Does nothing on other platforms. -// This forces the allocator to release freed memory. This is used to work -// around various Scudo inefficiencies. See b/170217718. -void MaybeReleaseAllocatorMemToOS(); +// TODO: As of Clang 19.0 std::from_chars is unimplemented for type double +// despite being part of C++17 standard, and already being supported by GCC and +// MSVC. Enable this once we have double support in Clang. +// inline std::optional StringViewToDouble(const base::StringView& sv) { +// return StringViewToNumber(sv); +// } -// geteuid() on POSIX OSes, returns 0 on Windows (See comment in utils.cc). -uid_t GetCurrentUserId(); +bool StartsWith(const std::string& str, const std::string& prefix); +bool EndsWith(const std::string& str, const std::string& suffix); +bool StartsWithAny(const std::string& str, + const std::vector& prefixes); +bool Contains(const std::string& haystack, const std::string& needle); +bool Contains(const std::string& haystack, char needle); +bool Contains(const std::vector& haystack, + const std::string& needle); +size_t Find(const StringView& needle, const StringView& haystack); +bool CaseInsensitiveEqual(std::string_view first, std::string_view second); +std::string Join(const std::vector& parts, + const std::string& delim); +std::vector SplitString(const std::string& text, + const std::string& delimiter); +std::string StripPrefix(const std::string& str, const std::string& prefix); +std::string StripSuffix(const std::string& str, const std::string& suffix); +std::string_view TrimWhitespace(std::string_view str); +std::string TrimWhitespace(const std::string& str); +std::string_view TrimWhitespace(const char* str); +std::string ToLower(const std::string& str); +std::string ToUpper(const std::string& str); +std::string StripChars(const std::string& str, + const std::string& chars, + char replacement); +std::string ToHex(const char* data, size_t size); +inline std::string ToHex(const std::string& s) { + return ToHex(s.c_str(), s.size()); +} +std::string IntToHexString(uint32_t number); +std::string Uint64ToHexString(uint64_t number); +std::string Uint64ToHexStringNoPrefix(uint64_t number); +std::string ReplaceAll(std::string str, + const std::string& to_replace, + const std::string& replacement); -// Forks the process. -// Parent: calls |parent_cb| with the child's PID and exits with its return -// value. The callback owns any startup output (e.g. printing the PID). -// Child: redirects stdio onto /dev/null, chdirs into / and returns. -void Daemonize(std::function parent_cb); +// Checks if all characters in the input string view `str` are ASCII. +// +// If so, the function returns true and `output` is not modified. +// If `str` contains non-ASCII characters, the function returns false, +// removes invalid UTF-8 characters from `str`, and stores the result in +// `output`. +bool CheckAsciiAndRemoveInvalidUTF8(base::StringView str, std::string& output); -// Returns the path of the current executable, e.g. /foo/bar/exe. -std::string GetCurExecutablePath(); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +bool WideToUTF8(const std::wstring& source, std::string& output); +bool UTF8ToWide(const std::string& source, std::wstring& output); +#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -// Returns the directory where the current executable lives in, e.g. /foo/bar. -// This is independent of cwd(). -std::string GetCurExecutableDir(); +// A BSD-style strlcpy without the return value. +// Copies at most |dst_size|-1 characters. Unlike strncpy, it always \0 +// terminates |dst|, as long as |dst_size| is not 0. +// Unlike strncpy and like strlcpy it does not zero-pad the rest of |dst|. +// Returns nothing. The BSD strlcpy returns the size of |src|, which might +// be > |dst_size|. Anecdotal experience suggests people assume the return value +// is the number of bytes written in |dst|. That assumption can lead to +// dangerous bugs. +// In order to avoid being subtly uncompliant with strlcpy AND avoid misuse, +// the choice here is to return nothing. +inline void StringCopy(char* dst, const char* src, size_t dst_size) { + for (size_t i = 0; i < dst_size; ++i) { + if ((dst[i] = src[i]) == '\0') { + return; // We hit and copied the null terminator. + } + } -// Memory returned by AlignedAlloc() must be freed via AlignedFree() not just -// free. It makes a difference on Windows where _aligned_malloc() and -// _aligned_free() must be paired. -// Prefer using the AlignedAllocTyped() below which takes care of the pairing. -void* AlignedAlloc(size_t alignment, size_t size); -void AlignedFree(void*); + // We were left off at dst_size. We over copied 1 byte. Null terminate. + if (PERFETTO_LIKELY(dst_size > 0)) + dst[dst_size - 1] = 0; +} -// Detects Sync-mode MTE (currently being tested in some Android builds). -// This is known to use extra memory for the stack history buffer. -bool IsSyncMemoryTaggingEnabled(); +// Like snprintf() but returns the number of chars *actually* written (without +// counting the null terminator) NOT "the number of chars which would have been +// written to the final string if enough space had been available". +// This should be used in almost all cases when the caller uses the return value +// of snprintf(). If the return value is not used, there is no benefit in using +// this wrapper, as this just calls snprintf() and mangles the return value. +// It always null-terminates |dst| (even in case of errors), unless +// |dst_size| == 0. +// Examples: +// SprintfTrunc(x, 4, "123whatever"): returns 3 and writes "123\0". +// SprintfTrunc(x, 4, "123"): returns 3 and writes "123\0". +// SprintfTrunc(x, 3, "123"): returns 2 and writes "12\0". +// SprintfTrunc(x, 2, "123"): returns 1 and writes "1\0". +// SprintfTrunc(x, 1, "123"): returns 0 and writes "\0". +// SprintfTrunc(x, 0, "123"): returns 0 and writes nothing. +// NOTE: This means that the caller has no way to tell when truncation happens +// vs the edge case of *just* fitting in the buffer. +size_t SprintfTrunc(char* dst, size_t dst_size, const char* fmt, ...) + PERFETTO_PRINTF_FORMAT(3, 4); -// A RAII version of the above, which takes care of pairing Aligned{Alloc,Free}. -template -struct AlignedDeleter { - inline void operator()(T* ptr) const { AlignedFree(ptr); } +// Line number starts from 1 +struct LineWithOffset { + base::StringView line; + uint32_t line_offset; + uint32_t line_num; }; -// The remove_extent here and below is to allow defining unique_ptr. -// As per https://en.cppreference.com/w/cpp/memory/unique_ptr the Deleter takes -// always a T*, not a T[]*. -template -using AlignedUniquePtr = - std::unique_ptr::type>>; - -template -AlignedUniquePtr AlignedAllocTyped(size_t n_membs) { - using TU = typename std::remove_extent::type; - return AlignedUniquePtr( - static_cast(AlignedAlloc(alignof(TU), sizeof(TU) * n_membs))); -} +// For given string and offset Pfinds a line with character for +// which offset points, what number is this line (starts from 1), and the offset +// inside this line. returns std::nullopt if the offset points to +// line break character or exceeds string length. +std::optional FindLineWithOffset(base::StringView str, + uint32_t offset); -// A RAII wrapper to invoke a function when leaving a function/scope. -template -class OnScopeExitWrapper { +// A helper class to facilitate construction and usage of write-once stack +// strings. +// Example usage: +// StackString<32> x("format %d %s", 42, string_arg); +// TakeString(x.c_str() | x.string_view() | x.ToStdString()); +// Rather than char x[32] + sprintf. +// Advantages: +// - Avoids useless zero-fills caused by people doing `char buf[32] {}` (mainly +// by fearing unknown snprintf failure modes). +// - Makes the code more robust in case of snprintf truncations (len() and +// string_view() will return the truncated length, unlike snprintf). +template +class StackString { public: - explicit OnScopeExitWrapper(Func f) : f_(std::move(f)), active_(true) {} - OnScopeExitWrapper(OnScopeExitWrapper&& other) noexcept - : f_(std::move(other.f_)), active_(other.active_) { - other.active_ = false; + explicit PERFETTO_PRINTF_FORMAT(/* 1=this */ 2, 3) + StackString(const char* fmt, ...) { + buf_[0] = '\0'; + va_list args; + va_start(args, fmt); + int res = vsnprintf(buf_, sizeof(buf_), fmt, args); + va_end(args); + buf_[sizeof(buf_) - 1] = '\0'; + len_ = res < 0 ? 0 : std::min(static_cast(res), sizeof(buf_) - 1); } - ~OnScopeExitWrapper() { - if (active_) - f_(); + + StringView string_view() const { return StringView(buf_, len_); } + std::string_view ToStdStringView() const { + return std::string_view(buf_, len_); } + std::string ToStdString() const { return std::string(buf_, len_); } + const char* c_str() const { return buf_; } + size_t len() const { return len_; } + char* mutable_data() { return buf_; } private: - Func f_; - bool active_; + char buf_[N]; + size_t len_ = 0; // Does not include the \0. }; -template -PERFETTO_WARN_UNUSED_RESULT OnScopeExitWrapper OnScopeExit(Func f) { - return OnScopeExitWrapper(std::move(f)); -} - -// Returns a xxd-style hex dump (hex + ascii chars) of the input data. -std::string HexDump(const void* data, size_t len, size_t bytes_per_line = 16); -inline std::string HexDump(const std::string& data, - size_t bytes_per_line = 16) { - return HexDump(data.data(), data.size(), bytes_per_line); -} - } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_UTILS_H_ /* * Copyright (C) 2021 The Android Open Source Project * @@ -1530,213 +1663,200 @@ inline std::string HexDump(const std::string& data, * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ -#define INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ +// gen_amalgamated expanded: #include "perfetto/ext/base/android_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/file_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_splitter.h" + +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -#include #include -// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" // For ssize_t. +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +#include +#endif + +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/base/time.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/string_utils.h" + +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) +#include +#include +#endif + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) +#include +#endif namespace perfetto { namespace base { -// Returns the length of the destination string (included '=' padding). -// Does NOT include the size of the string null terminator. -inline size_t Base64EncSize(size_t src_size) { - return (src_size + 2) / 3 * 4; -} +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -// Returns the upper bound on the length of the destination buffer. -// The actual decoded length might be <= the number returned here. -inline size_t Base64DecSize(size_t src_size) { - return (src_size + 3) / 4 * 3; +std::string GetAndroidProp(const char* name) { + std::string ret; +#if __ANDROID_API__ >= 26 + const prop_info* pi = __system_property_find(name); + if (!pi) { + return ret; + } + __system_property_read_callback( + pi, + [](void* dst_void, const char*, const char* value, uint32_t) { + std::string& dst = *static_cast(dst_void); + dst = value; + }, + &ret); +#else // __ANDROID_API__ < 26 + char value_buf[PROP_VALUE_MAX]; + int len = __system_property_get(name, value_buf); + if (len > 0 && static_cast(len) < sizeof(value_buf)) { + ret = std::string(value_buf, static_cast(len)); + } +#endif + return ret; } -// Does NOT null-terminate |dst|. -ssize_t Base64Encode(const void* src, - size_t src_size, - char* dst, - size_t dst_size); - -std::string Base64Encode(const void* src, size_t src_size); +#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) -inline std::string Base64Encode(StringView sv) { - return Base64Encode(sv.data(), sv.size()); +Utsname GetUtsname() { + Utsname utsname_info; +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) + struct utsname uname_info; + if (uname(&uname_info) == 0) { + utsname_info.sysname = uname_info.sysname; + utsname_info.version = uname_info.version; + utsname_info.machine = uname_info.machine; + utsname_info.release = uname_info.release; + } else { + PERFETTO_ELOG("Unable to read Utsname information"); + } +#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + return utsname_info; } -// Returns -1 in case of failure. -ssize_t Base64Decode(const char* src, - size_t src_size, - uint8_t* dst, - size_t dst_size); +SystemInfo GetSystemInfo() { + SystemInfo info; -std::optional Base64Decode(const char* src, size_t src_size); - -inline std::optional Base64Decode(StringView sv) { - return Base64Decode(sv.data(), sv.size()); -} - -} // namespace base -} // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// gen_amalgamated expanded: #include "perfetto/ext/base/base64.h" - -namespace perfetto { -namespace base { - -namespace { + info.timezone_off_mins = GetTimezoneOffsetMins(); -constexpr char kPadding = '='; +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_NACL) && \ + !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) -constexpr char kEncTable[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static_assert(sizeof(kEncTable) == (1u << 6) + sizeof('\0'), "Bad table size"); + info.utsname_info = GetUtsname(); + info.page_size = static_cast(sysconf(_SC_PAGESIZE)); + info.num_cpus = static_cast(sysconf(_SC_NPROCESSORS_CONF)); -// Maps an ASCII character to its 6-bit value. It only contains translations -// from '+' to 'z'. Supports the standard (+/) and URL-safe (-_) alphabets. -constexpr uint8_t kX = 0xff; // Value used for invalid characters -constexpr uint8_t kDecTable[] = { - 62, kX, 62, kX, 63, 52, 53, 54, 55, 56, // 00 - 09 - 57, 58, 59, 60, 61, kX, kX, kX, 0, kX, // 10 - 19 - kX, kX, 0, 1, 2, 3, 4, 5, 6, 7, // 20 - 29 - 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, // 30 - 39 - 18, 19, 20, 21, 22, 23, 24, 25, kX, kX, // 40 - 49 - kX, kX, 63, kX, 26, 27, 28, 29, 30, 31, // 50 - 59 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, // 60 - 69 - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 70 - 79 -}; -constexpr char kMinDecChar = '+'; -constexpr char kMaxDecChar = 'z'; -static_assert(kMaxDecChar - kMinDecChar <= sizeof(kDecTable), "Bad table size"); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) || \ + PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + // Use the Linux-specific sysinfo() system call on Linux and Android. + // https://man7.org/linux/man-pages/man2/sysinfo.2.html + struct sysinfo sys_info; + if (sysinfo(&sys_info) == 0) { + info.system_ram_bytes = + static_cast(sys_info.totalram) * sys_info.mem_unit; + } +#else + // POSIX Fallback (macOS, BSD, etc.): Use sysconf() to get physical pages. + long pages = sysconf(_SC_PHYS_PAGES); + if (pages > 0 && info.page_size.has_value()) { + info.system_ram_bytes = static_cast(pages) * (*info.page_size); + } +#endif -inline uint8_t DecodeChar(char c) { - if (c < kMinDecChar || c > kMaxDecChar) - return kX; - return kDecTable[c - kMinDecChar]; -} +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX_BUT_NOT_QNX) + // Extract the device compatible strings from the device tree (if available) + const std::string path = "/sys/firmware/devicetree/base/compatible"; + std::string device_compatible_data; + if (ReadFile(path, &device_compatible_data)) { + for (base::StringSplitter ss(std::move(device_compatible_data), '\0'); + ss.Next();) { + info.device_tree_compatibles.emplace_back(ss.cur_token(), + ss.cur_token_size()); + PERFETTO_DLOG("Found linux device compatible: %s", ss.cur_token()); + } + } else { + PERFETTO_DLOG("No linux device compatible found in path: %s", path.c_str()); + } +#endif +#endif // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -} // namespace +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + info.android_build_fingerprint = GetAndroidProp("ro.build.fingerprint"); + if (info.android_build_fingerprint.empty()) { + PERFETTO_ELOG("Unable to read ro.build.fingerprint"); + } -ssize_t Base64Encode(const void* src, - size_t src_size, - char* dst, - size_t dst_size) { - const size_t padded_dst_size = Base64EncSize(src_size); - if (dst_size < padded_dst_size) - return -1; // Not enough space in output. + info.android_device_manufacturer = GetAndroidProp("ro.product.manufacturer"); + if (info.android_device_manufacturer.empty()) { + PERFETTO_ELOG("Unable to read ro.product.manufacturer"); + } - const uint8_t* rd = static_cast(src); - const uint8_t* const end = rd + src_size; - size_t wr_size = 0; - while (rd < end) { - uint8_t s[3]{}; - s[0] = *(rd++); - dst[wr_size++] = kEncTable[s[0] >> 2]; + std::string sdk_str_value = GetAndroidProp("ro.build.version.sdk"); + info.android_sdk_version = StringToUInt64(sdk_str_value); + if (!info.android_sdk_version.has_value()) { + PERFETTO_ELOG("Unable to read ro.build.version.sdk"); + } - uint8_t carry0 = static_cast((s[0] & 0x03) << 4); - if (PERFETTO_LIKELY(rd < end)) { - s[1] = *(rd++); - dst[wr_size++] = kEncTable[carry0 | (s[1] >> 4)]; - } else { - dst[wr_size++] = kEncTable[carry0]; - dst[wr_size++] = kPadding; - dst[wr_size++] = kPadding; - break; - } + info.android_soc_model = GetAndroidProp("ro.soc.model"); + if (info.android_soc_model.empty()) { + PERFETTO_ELOG("Unable to read ro.soc.model"); + } - uint8_t carry1 = static_cast((s[1] & 0x0f) << 2); - if (PERFETTO_LIKELY(rd < end)) { - s[2] = *(rd++); - dst[wr_size++] = kEncTable[carry1 | (s[2] >> 6)]; - } else { - dst[wr_size++] = kEncTable[carry1]; - dst[wr_size++] = kPadding; - break; - } + // guest_soc model is not always present + info.android_guest_soc_model = GetAndroidProp("ro.boot.guest_soc.model"); - dst[wr_size++] = kEncTable[s[2] & 0x3f]; + info.android_hardware_revision = GetAndroidProp("ro.boot.hardware.revision"); + if (info.android_hardware_revision.empty()) { + PERFETTO_ELOG("Unable to read ro.boot.hardware.revision"); } - PERFETTO_DCHECK(wr_size == padded_dst_size); - return static_cast(padded_dst_size); -} - -std::string Base64Encode(const void* src, size_t src_size) { - std::string dst; - dst.resize(Base64EncSize(src_size)); - auto res = Base64Encode(src, src_size, &dst[0], dst.size()); - PERFETTO_CHECK(res == static_cast(dst.size())); - return dst; -} -ssize_t Base64Decode(const char* src, - size_t src_size, - uint8_t* dst, - size_t dst_size) { - const size_t min_dst_size = Base64DecSize(src_size); - if (dst_size < min_dst_size) - return -1; + info.android_storage_model = GetAndroidProp("ro.boot.hardware.ufs"); + if (info.android_storage_model.empty()) { + PERFETTO_ELOG("Unable to read ro.boot.hardware.ufs"); + } - const char* rd = src; - const char* const end = src + src_size; - size_t wr_size = 0; + info.android_ram_model = GetAndroidProp("ro.boot.hardware.ddr"); + if (info.android_ram_model.empty()) { + PERFETTO_ELOG("Unable to read ro.boot.hardware.ddr"); + } - char s[4]{}; - while (rd < end) { - uint8_t d[4]; - for (uint32_t j = 0; j < 4; j++) { - // Padding is only feasible for the last 2 chars of each group of 4. - s[j] = rd < end ? *(rd++) : (j < 2 ? '\0' : kPadding); - d[j] = DecodeChar(s[j]); - if (d[j] == kX) - return -1; // Invalid input char. - } - dst[wr_size] = static_cast((d[0] << 2) | (d[1] >> 4)); - dst[wr_size + 1] = static_cast((d[1] << 4) | (d[2] >> 2)); - dst[wr_size + 2] = static_cast((d[2] << 6) | (d[3])); - wr_size += 3; + info.android_serial_console = GetAndroidProp("init.svc.console"); + if (info.android_serial_console.empty()) { + PERFETTO_ELOG("Unable to read init.svc.console"); } +#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) - PERFETTO_CHECK(wr_size <= dst_size); - wr_size -= (s[3] == kPadding ? 1 : 0) + (s[2] == kPadding ? 1 : 0); - return static_cast(wr_size); + return info; } -std::optional Base64Decode(const char* src, size_t src_size) { - std::string dst; - dst.resize(Base64DecSize(src_size)); - auto res = Base64Decode(src, src_size, reinterpret_cast(&dst[0]), - dst.size()); - if (res < 0) - return std::nullopt; // Decoding error. - - PERFETTO_CHECK(res <= static_cast(dst.size())); - dst.resize(static_cast(res)); - return std::make_optional(dst); +std::string GetPerfettoMachineName() { + const char* env_name = getenv("PERFETTO_MACHINE_NAME"); + if (env_name) { + return env_name; + } +#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) + std::string name = base::GetAndroidProp("traced.machine_name"); + if (name.empty()) { + name = GetUtsname().sysname; + } + return name; +#else + return GetUtsname().sysname; +#endif } } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/cpu_info.cc -// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info.h +// gen_amalgamated begin source: src/base/atomic_file.cc +// gen_amalgamated begin header: include/perfetto/ext/base/atomic_file.h /* * Copyright (C) 2026 The Android Open Source Project * @@ -1753,42 +1873,45 @@ std::optional Base64Decode(const char* src, size_t src_size) { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_ATOMIC_FILE_H_ +#define INCLUDE_PERFETTO_EXT_BASE_ATOMIC_FILE_H_ -#include -#include #include -#include - -namespace perfetto { -namespace base { - -struct CpuInfo { - std::string processor; - uint32_t cpu_index = 0; - std::optional implementer; - std::optional architecture; - std::optional variant; - std::optional part; - std::optional revision; - uint64_t features = 0; - char arm_cpuid[32] = {}; -}; +// gen_amalgamated expanded: #include "perfetto/base/status.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" -// Parses the contents of the input string into per-CPU entries. -std::vector ParseCpuInfo(std::string proc_cpu_info); +namespace perfetto::base { -// Reads /proc/cpuinfo and parses it into per-CPU entries. -std::vector ReadCpuInfo(); +// Writes a sibling temporary file and atomically replaces the destination on +// Commit(). Until then an existing destination is untouched. Does not follow +// destination symlinks. An uncommitted temporary file is deleted on +// destruction. +class AtomicFile { + public: + explicit AtomicFile(const std::string& destination); + ~AtomicFile(); + AtomicFile(const AtomicFile&) = delete; + AtomicFile& operator=(const AtomicFile&) = delete; + + Status Open(); + ScopedFile DuplicateFD() const; + // Final operation: close all DuplicateFD() handles before committing. + // On failure, destruction still attempts to remove the temporary file. + Status Commit() &&; + const std::string& temp_path() const { return temp_path_; } -} // namespace base -} // namespace perfetto + private: + const std::string destination_; + const std::string temp_path_; + ScopedFile fd_; + bool owns_temp_file_ = false; +}; -#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info_features_allowlist.h +} // namespace perfetto::base +#endif // INCLUDE_PERFETTO_EXT_BASE_ATOMIC_FILE_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/uuid.h /* - * Copyright (C) 2025 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1803,29 +1926,75 @@ std::vector ReadCpuInfo(); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ -#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_UUID_H_ +#define INCLUDE_PERFETTO_EXT_BASE_UUID_H_ + +#include +#include +#include +#include + +// gen_amalgamated expanded: #include "perfetto/base/endian.h" +// gen_amalgamated expanded: #include "perfetto/base/export.h" namespace perfetto { namespace base { -// APPEND ONLY. DO NOT EVER REMOVE ENTRIES FROM THIS ARRAY OR REORDER. -// This array is used both by traced_probes and trace_processor to index the -// cpuinfo flags. Changing the order will break trace_processor compatibility -// with old traces. -constexpr const char* kCpuInfoFeatures[] = { - "mte", // DO NOT REMOVE/REODER. - "mte3", // DO NOT REMOVE/REODER. +class PERFETTO_EXPORT_COMPONENT Uuid { + public: + explicit Uuid(const std::string& s); + explicit Uuid(int64_t lsb, int64_t msb); + Uuid(); + + std::array* data() { return &data_; } + const std::array* data() const { return &data_; } + + bool operator==(const Uuid& other) const { return data_ == other.data_; } + + bool operator!=(const Uuid& other) const { return !(*this == other); } + + explicit operator bool() const { return *this != Uuid(); } + + int64_t msb() const { + uint64_t result; + memcpy(&result, data_.data() + 8, 8); + return static_cast(LE64ToHost(result)); + } + + int64_t lsb() const { + uint64_t result; + memcpy(&result, data_.data(), 8); + return static_cast(LE64ToHost(result)); + } + + void set_lsb_msb(int64_t lsb, int64_t msb) { + set_lsb(lsb); + set_msb(msb); + } + void set_msb(int64_t msb) { + uint64_t le = HostToLE64(static_cast(msb)); + memcpy(data_.data() + 8, &le, 8); + } + void set_lsb(int64_t lsb) { + uint64_t le = HostToLE64(static_cast(lsb)); + memcpy(data_.data(), &le, 8); + } + + std::string ToString() const; + std::string ToPrettyString() const; + + private: + std::array data_{}; }; +Uuid Uuidv4(); + } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/file_utils.h -// gen_amalgamated begin header: include/perfetto/base/status.h +#endif // INCLUDE_PERFETTO_EXT_BASE_UUID_H_ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2026 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1840,108 +2009,108 @@ constexpr const char* kCpuInfoFeatures[] = { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_ -#define INCLUDE_PERFETTO_BASE_STATUS_H_ +// gen_amalgamated expanded: #include "perfetto/ext/base/atomic_file.h" -#include -#include -#include -#include +#include +#include +#include +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" // gen_amalgamated expanded: #include "perfetto/base/compiler.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" // gen_amalgamated expanded: #include "perfetto/base/logging.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/file_utils.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/uuid.h" -namespace perfetto { -namespace base { - -// Represents either the success or the failure message of a function. -// This can used as the return type of functions which would usually return an -// bool for success or int for errno but also wants to add some string context -// (ususally for logging). -// -// Similar to absl::Status, an optional "payload" can also be included with more -// context about the error. This allows passing additional metadata about the -// error (e.g. location of errors, potential mitigations etc). -class PERFETTO_EXPORT_COMPONENT Status { - public: - Status() : ok_(true) {} - explicit Status(std::string msg) : ok_(false), message_(std::move(msg)) { - PERFETTO_CHECK(!message_.empty()); - } - - // Copy operations. - Status(const Status&) = default; - Status& operator=(const Status&) = default; - - // Move operations. The moved-from state is valid but unspecified. - Status(Status&&) noexcept = default; - Status& operator=(Status&&) = default; - - bool ok() const { return ok_; } - - // When ok() is false this returns the error message. Returns the empty string - // otherwise. - const std::string& message() const { return message_; } - const char* c_message() const { return message_.c_str(); } - - ////////////////////////////////////////////////////////////////////////////// - // Payload Management APIs - ////////////////////////////////////////////////////////////////////////////// - - // Payloads can be attached to error statuses to provide additional context. - // - // Payloads are (key, value) pairs, where the key is a string acting as a - // unique "type URL" and the value is an opaque string. The "type URL" should - // be unique, follow the format of a URL and, ideally, documentation on how to - // interpret its associated data should be available. - // - // To attach a payload to a status object, call `Status::SetPayload()`. - // Similarly, to extract the payload from a status, call - // `Status::GetPayload()`. - // - // Note: the payload APIs are only meaningful to call when the status is an - // error. Otherwise, all methods are noops. - - // Gets the payload for the given |type_url| if one exists. - // - // Will always return std::nullopt if |ok()|. - std::optional GetPayload(std::string_view type_url) const; - - // Sets the payload for the given key. The key should - // - // Will always do nothing if |ok()|. - void SetPayload(std::string_view type_url, std::string value); - - // Erases the payload for the given string and returns true if the payload - // existed and was erased. - // - // Will always do nothing if |ok()|. - bool ErasePayload(std::string_view type_url); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include +#include +#else +#include +#include +#endif - private: - struct Payload { - std::string type_url; - std::string payload; - }; +namespace perfetto::base { - bool ok_ = false; - std::string message_; - std::vector payloads_; -}; +AtomicFile::AtomicFile(const std::string& destination) + : destination_(destination), + temp_path_(destination + ".tmp." + Uuidv4().ToPrettyString()) {} -// Returns a status object which represents the Ok status. -inline Status OkStatus() { - return Status(); +AtomicFile::~AtomicFile() { + fd_.reset(); + if (owns_temp_file_) + Unlink(temp_path_.c_str()); } -Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); +base::Status AtomicFile::Open() { + PERFETTO_CHECK(!fd_ && !owns_temp_file_); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + DWORD attributes = GetFileAttributesA(destination_.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && + (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))) + return base::ErrStatus( + "output must be a regular file, not a directory or link"); + HANDLE handle = + CreateFileA(temp_path_.c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) + return base::ErrStatus( + "cannot create temporary output beside '%s' (Windows error %lu)", + destination_.c_str(), GetLastError()); + owns_temp_file_ = true; + fd_.reset(_open_osfhandle(reinterpret_cast(handle), _O_BINARY)); + if (!fd_) + CloseHandle(handle); +#else + struct stat output{}; + bool exists = lstat(destination_.c_str(), &output) == 0; + PERFETTO_MSAN_UNPOISON(&output, sizeof(output)); + if (exists && !S_ISREG(output.st_mode)) + return base::ErrStatus( + "output must be a regular file, not a directory or link: '%s'. Specify " + "the target path directly.", + destination_.c_str()); + fd_ = base::OpenFile(temp_path_, O_CREAT | O_EXCL | O_RDWR, 0644); + owns_temp_file_ = static_cast(fd_); + if (fd_ && exists && fchmod(*fd_, output.st_mode & 0777) != 0) + return base::ErrStatus("cannot preserve output permissions: %s", + strerror(errno)); +#endif + if (!fd_) + return base::ErrStatus( + "cannot create temporary output beside '%s': %s. Check that the parent " + "directory exists and is writable.", + destination_.c_str(), strerror(errno)); + return base::OkStatus(); +} -} // namespace base -} // namespace perfetto +base::ScopedFile AtomicFile::DuplicateFD() const { + PERFETTO_CHECK(fd_); + return DupFile(*fd_); +} -#endif // INCLUDE_PERFETTO_BASE_STATUS_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/scoped_file.h +base::Status AtomicFile::Commit() && { + PERFETTO_CHECK(fd_); + if (!base::FlushFile(*fd_)) + return base::ErrStatus("failed to flush output: %s", strerror(errno)); + fd_.reset(); +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + if (!MoveFileExA(temp_path_.c_str(), destination_.c_str(), + MOVEFILE_REPLACE_EXISTING)) + return base::ErrStatus("failed to publish '%s' (Windows error %lu)", + destination_.c_str(), GetLastError()); +#else + if (rename(temp_path_.c_str(), destination_.c_str()) != 0) + return base::ErrStatus("failed to publish '%s': %s", destination_.c_str(), + strerror(errno)); +#endif + owns_temp_file_ = false; + return base::OkStatus(); +} +} // namespace perfetto::base +// gen_amalgamated begin source: src/base/base64.cc +// gen_amalgamated begin header: include/perfetto/ext/base/base64.h +// gen_amalgamated begin header: include/perfetto/ext/base/utils.h /* * Copyright (C) 2017 The Android Open Source Project * @@ -1958,358 +2127,346 @@ Status ErrStatus(const char* format, ...) PERFETTO_PRINTF_FORMAT(1, 2); * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ -#define INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ - -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +#ifndef INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ +#include +#include +#include #include +#include +#include -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -#include // For DIR* / opendir(). -#endif - +#include +#include +#include +#include #include -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/logging.h" -// gen_amalgamated expanded: #include "perfetto/base/platform_handle.h" +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +// Even if Windows has errno.h, the all syscall-restart behavior does not apply. +// Trying to handle EINTR can cause more harm than good if errno is left stale. +// Chromium does the same. +#define PERFETTO_EINTR(x) (x) +#else +#define PERFETTO_EINTR(x) \ + ([&] { \ + decltype(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + } while (eintr_wrapper_result == -1 && errno == EINTR); \ + return eintr_wrapper_result; \ + }()) +#endif namespace perfetto { namespace base { namespace internal { -// Used for the most common cases of ScopedResource where there is only one -// invalid value. -template -struct DefaultValidityChecker { - static bool IsValid(T t) { return t != InvalidValue; } -}; +extern std::atomic g_cached_page_size; +uint32_t GetSysPageSizeSlowpath(); } // namespace internal -// RAII classes for auto-releasing fds and dirs. -// if T is a pointer type, InvalidValue must be nullptr. Doing otherwise -// causes weird unexpected behaviors (See https://godbolt.org/z/5nGMW4). -template > -class ScopedResource { - public: - using ValidityChecker = Checker; - static constexpr T kInvalid = InvalidValue; - - explicit ScopedResource(T t = InvalidValue) : t_(t) {} - ScopedResource(ScopedResource&& other) noexcept : t_(other.t_) { - other.t_ = InvalidValue; - } - ScopedResource& operator=(ScopedResource&& other) { - reset(other.t_); - other.t_ = InvalidValue; - return *this; - } - T get() const { return t_; } - T operator*() const { return t_; } - explicit operator bool() const { return Checker::IsValid(t_); } - void reset(T r = InvalidValue) { - if (Checker::IsValid(t_)) { - int res = CloseFunction(t_); - if (CheckClose) - PERFETTO_CHECK(res == 0); - } - t_ = r; - } - T release() { - T t = t_; - t_ = InvalidValue; - return t; - } - ~ScopedResource() { reset(InvalidValue); } - - private: - ScopedResource(const ScopedResource&) = delete; - ScopedResource& operator=(const ScopedResource&) = delete; - T t_; -}; - -// Declared in file_utils.h. Forward declared to avoid #include cycles. -int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); +// Returns the system's page size. Use this when dealing with mmap, madvise and +// similar mm-related syscalls. +// This function might be called in hot paths. Avoid calling getpagesize() all +// the times, in many implementations getpagesize() calls sysconf() which is +// not cheap. +inline uint32_t GetSysPageSize() { + const uint32_t page_size = + internal::g_cached_page_size.load(std::memory_order_relaxed); + return page_size != 0 ? page_size : internal::GetSysPageSizeSlowpath(); +} -// Use this for file resources obtained via open() and similar APIs. -using ScopedFile = ScopedResource; -using ScopedFstream = ScopedResource; +template +constexpr size_t ArraySize(const T (&)[TSize]) { + return TSize; +} -// Use this for resources that are HANDLE on Windows. See comments in -// platform_handle.h -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -using ScopedPlatformHandle = ScopedResource; +// Adds `a` and `b` into `*result`, returning false on overflow. The value of +// `*result` is unspecified when false is returned. +inline bool CheckedAdd(int64_t a, int64_t b, int64_t* result) { +#if defined(__clang__) || defined(__GNUC__) + return !__builtin_add_overflow(a, b, result); #else -// On non-windows systems we alias ScopedPlatformHandle to ScopedFile because -// they are really the same. This is to allow assignments between the two in -// Linux-specific code paths that predate ScopedPlatformHandle. -static_assert(std::is_same::value, ""); -using ScopedPlatformHandle = ScopedFile; - -// DIR* does not exist on Windows. -using ScopedDir = ScopedResource; + constexpr int64_t kMax = std::numeric_limits::max(); + constexpr int64_t kMin = std::numeric_limits::min(); + if ((b > 0 && a > kMax - b) || (b < 0 && a < kMin - b)) + return false; + *result = a + b; + return true; #endif +} -} // namespace base -} // namespace perfetto - -#endif // INCLUDE_PERFETTO_EXT_BASE_SCOPED_FILE_H_ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ -#define INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ - -#include // For mode_t & O_RDONLY/RDWR. Exists also on Windows. -#include -#include - -#include -#include -#include -#include -#include - -// gen_amalgamated expanded: #include "perfetto/base/build_config.h" -// gen_amalgamated expanded: #include "perfetto/base/export.h" -// gen_amalgamated expanded: #include "perfetto/base/status.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/scoped_file.h" -// gen_amalgamated expanded: #include "perfetto/ext/base/sys_types.h" - -namespace perfetto { -namespace base { - -class TaskRunner; - -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -using FileOpenMode = int; -inline constexpr char kDevNull[] = "NUL"; -inline constexpr char kFopenReadFlag[] = "r"; +// Subtracts `b` from `a` into `*result`, returning false on overflow. The +// value of `*result` is unspecified when false is returned. +inline bool CheckedSub(int64_t a, int64_t b, int64_t* result) { +#if defined(__clang__) || defined(__GNUC__) + return !__builtin_sub_overflow(a, b, result); #else -using FileOpenMode = mode_t; -inline constexpr char kDevNull[] = "/dev/null"; -inline constexpr char kFopenReadFlag[] = "re"; + constexpr int64_t kMax = std::numeric_limits::max(); + constexpr int64_t kMin = std::numeric_limits::min(); + if ((b < 0 && a > kMax + b) || (b > 0 && a < kMin + b)) + return false; + *result = a - b; + return true; #endif +} -constexpr FileOpenMode kFileModeInvalid = static_cast(-1); - -// Cross-platform variant of ReadFileDescriptor() that takes a PlatformHandle. -// On Windows normalizes ERROR_BROKEN_PIPE to EOF so behavior matches POSIX. -bool ReadPlatformHandle(PlatformHandle, std::string* out); - -// Reads from |fd|, appending what is currently available into |*out|. -// Returns: -// True: EOF reached (all writers of |fd| have closed their end). -// False: read error. On a non-blocking |fd| this includes EAGAIN (no data -// currently available but writers are still alive); callers can check -// IsAgain(errno) and retry on the next readability notification. -bool ReadFileDescriptor(int fd, std::string* out); +// Returns whether `value` is positive or negative zero. Compares the bit +// pattern so it can be used in translation units compiled with -Wfloat-equal. +inline bool IsZero(double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof(bits)); + return (bits << 1) == 0; +} +inline bool IsZero(int64_t value) { + return value == 0; +} -// Convenience wrapper around ReadFileDescriptor() that takes a FILE*. -bool ReadFileStream(FILE* f, std::string* out); +// Returns the bits of `value` with -0.0 turned into 0.0 and every NaN into +// the same NaN, so equal values, and all NaNs, have the same bits. +inline uint64_t CanonicalFloatBits(double value) { + uint64_t bits; + memcpy(&bits, &value, sizeof(bits)); + if (PERFETTO_UNLIKELY((bits << 1) == 0)) { + return 0; + } + if (PERFETTO_UNLIKELY((bits << 1) > (uint64_t{0x7FF} << 53))) { + double nan = std::numeric_limits::quiet_NaN(); + memcpy(&bits, &nan, sizeof(bits)); + } + return bits; +} +inline uint32_t CanonicalFloatBits(float value) { + uint32_t bits; + memcpy(&bits, &value, sizeof(bits)); + if (PERFETTO_UNLIKELY((bits << 1) == 0)) { + return 0; + } + if (PERFETTO_UNLIKELY((bits << 1) > (uint32_t{0xFF} << 24))) { + float nan = std::numeric_limits::quiet_NaN(); + memcpy(&bits, &nan, sizeof(bits)); + } + return bits; +} -// Opens |path| read-only and reads its contents into |*out|. -// Returns false if the file cannot be opened. -bool ReadFile(const std::string& path, std::string* out); +// Adds `a` and `b`, clamping to INT64_MIN / INT64_MAX on overflow instead of +// wrapping (which is UB and can flip the sign of the result). +inline int64_t SaturatingAdd(int64_t a, int64_t b) { + constexpr int64_t kMax = std::numeric_limits::max(); + constexpr int64_t kMin = std::numeric_limits::min(); +#if defined(__clang__) || defined(__GNUC__) + int64_t result; + if (PERFETTO_UNLIKELY(__builtin_add_overflow(a, b, &result))) + return a < 0 ? kMin : kMax; + return result; +#else + if (b > 0 && a > kMax - b) + return kMax; + if (b < 0 && a < kMin - b) + return kMin; + return a + b; +#endif +} -// A wrapper around read(2). It deals with Linux vs Windows includes. It also -// deals with handling EINTR. Has the same semantics of UNIX's read(2). -ssize_t Read(int fd, void* dst, size_t dst_size); +// Multiplies `a` by `b`, clamping to INT64_MIN / INT64_MAX on overflow instead +// of wrapping (which is UB and can flip the sign of the result). +inline int64_t SaturatingMultiply(int64_t a, int64_t b) { + constexpr int64_t kMax = std::numeric_limits::max(); + constexpr int64_t kMin = std::numeric_limits::min(); +#if defined(__clang__) || defined(__GNUC__) + int64_t result; + if (PERFETTO_UNLIKELY(__builtin_mul_overflow(a, b, &result))) { + // On overflow the true product's sign is the XOR of the operands' signs. + // Neither operand can be zero here (0 never overflows). + return ((a < 0) == (b < 0)) ? kMax : kMin; + } + return result; +#else + // Portable fallback (e.g. MSVC cl.exe): detect overflow *before* multiplying + // by dividing a limit by one operand, which never itself overflows. + if (a == 0 || b == 0) + return 0; + if ((a < 0) == (b < 0)) { // Same sign: product is positive. + if (a > 0 ? (a > kMax / b) : (a < kMax / b)) + return kMax; + } else { // Opposite signs: product is negative. + if (a > 0 ? (b < kMin / a) : (a < kMin / b)) + return kMin; + } + return a * b; +#endif +} -// Call write until all data is written or an error is detected. +// Function object which invokes 'free' on its parameter, which must be +// a pointer. Can be used to store malloc-allocated pointers in std::unique_ptr: // -// man 2 write: -// If a write() is interrupted by a signal handler before any bytes are -// written, then the call fails with the error EINTR; if it is -// interrupted after at least one byte has been written, the call -// succeeds, and returns the number of bytes written. -ssize_t WriteAll(int fd, const void* buf, size_t count); - -// Copies all data from |fd_in| to |fd_out|. Saves the offset of |fd_in|, -// rewinds it to the beginning, copies the content, and restores the offset. -// |fd_in| can't be a pipe, socket of FIFO. -base::Status CopyFileContents(int fd_in, int fd_out); - -ssize_t WriteAllHandle(PlatformHandle, const void* buf, size_t count); +// std::unique_ptr foo_ptr( +// static_cast(malloc(sizeof(int)))); +struct FreeDeleter { + inline void operator()(void* ptr) const { free(ptr); } +}; -ScopedFile OpenFile(const std::string& path, - int flags, - FileOpenMode = kFileModeInvalid); -ScopedFstream OpenFstream(const std::string& path, const std::string& mode); +template +constexpr T AssumeLittleEndian(T value) { +#if !PERFETTO_IS_LITTLE_ENDIAN() + static_assert(false, "Unimplemented on big-endian archs"); +#endif + return value; +} -// This is an alias for close(). It's to avoid leaking windows.h in headers. -// Exported because ScopedFile is used in the /include/ext API by Chromium -// component builds. -int PERFETTO_EXPORT_COMPONENT CloseFile(int fd); +// Round up |size| to a multiple of |alignment| (must be a power of two). +inline constexpr size_t AlignUp(size_t size, size_t alignment) { + return (size + alignment - 1) & ~(alignment - 1); +} -bool FlushFile(int fd); +// Round down |size| to a multiple of |alignment| (must be a power of two). +inline constexpr size_t AlignDown(size_t size, size_t alignment) { + return size & ~(alignment - 1); +} -// Moves the file offset to |offset| bytes from the beginning of the file. -// Returns false if |offset| cannot be represented by the platform or the seek -// fails. -bool SeekFile(int fd, uint64_t offset); +template +inline constexpr bool IsPowerOfTwo(T x) { + static_assert(std::is_unsigned_v && std::is_integral_v, + "T must be an unsigned integer"); + return x != 0 && (x & (x - 1)) == 0; +} -// Changes the size of an open file to |size| bytes. -// Returns false if |size| cannot be represented by the platform or truncation -// fails. -bool TruncateFile(int fd, uint64_t size); +// Returns the smallest power of two greater than or equal to |x|. Returns zero +// for zero or when the result is not representable by T. +template +inline constexpr T RoundUpToPowerOfTwo(T x) { + static_assert(std::is_unsigned_v && std::is_integral_v, + "T must be an unsigned integer"); + if (x == 0) { + return 0; + } + --x; + for (size_t shift = 1; shift < sizeof(T) * 8; shift *= 2) { + x |= x >> shift; + } + return ++x; +} -// Returns true if mkdir succeeds, false if it fails (see errno in that case). -// `mode` is the permission bits for the new directory; it is ignored on -// Windows. -bool Mkdir(const std::string& path, uint32_t mode = 0755); +// TODO(primiano): clean this up and move all existing usages to the constexpr +// version above. +template +constexpr size_t AlignUp(size_t size) { + static_assert(IsPowerOfTwo(alignment), "alignment must be a pow2"); + return AlignUp(size, alignment); +} -// Calls rmdir() on UNIX, _rmdir() on Windows. -bool Rmdir(const std::string& path); +inline bool IsAgain(int err) { + return err == EAGAIN || err == EWOULDBLOCK; +} -// Removes a file: unlink() on UNIX, _unlink() on Windows. Takes a const char* -// and is async-signal-safe on POSIX, so it's callable from a signal handler. -bool Unlink(const char* path); +// setenv(2)-equivalent. Deals with Windows vs Posix discrepancies. +void SetEnv(const std::string& key, const std::string& value); -// Wrapper around access(path, F_OK). -bool FileExists(const std::string& path); +// unsetenv(2)-equivalent. Deals with Windows vs Posix discrepancies. +void UnsetEnv(const std::string& key); -// Returns true if the path exists and is a directory. -bool DirectoryExists(const std::string& path); +// Returns true if |fd| is connected to an interactive terminal (TTY). Deals +// with Windows vs Posix discrepancies (isatty() vs _isatty()). +bool IsTty(int fd); -// Gets the extension for a filename. If the file has two extensions, returns -// only the last one (foo.pb.gz => .gz). Returns empty string if there is no -// extension. -std::string GetFileExtension(const std::string& filename); +// Convenience overload for C stdio streams (e.g. stdin/stdout/stderr). +bool IsTty(FILE* stream); -// Returns the basename component of a path (the final component after the last -// directory separator). Behaves like man 2 basename, but works with both '/' -// and '\' separators for cross-platform compatibility. -// Examples: -// Basename("/usr/bin/ls") => "ls" -// Basename("/usr/bin/") => "bin" -// Basename("/") => "/" -// Basename("foo") => "foo" -// Basename("") => "." -// Basename("C:\\Windows\\System32") => "System32" -std::string Basename(const std::string& path); +// Calls mallopt(M_PURGE, 0) on Android. Does nothing on other platforms. +// This forces the allocator to release freed memory. This is used to work +// around various Scudo inefficiencies. See b/170217718. +void MaybeReleaseAllocatorMemToOS(); -// Returns the directory component of a path (everything up to but not -// including the final component). Behaves like man 2 dirname, but works with -// both '/' and '\' separators for cross-platform compatibility. -// Examples: -// Dirname("/usr/bin/ls") => "/usr/bin" -// Dirname("/usr/bin") => "/usr" -// Dirname("/") => "/" -// Dirname("foo") => "." -// Dirname("") => "." -// Dirname("C:\\Windows\\System32") => "C:\\Windows" -std::string Dirname(const std::string& path); +// geteuid() on POSIX OSes, returns 0 on Windows (See comment in utils.cc). +uid_t GetCurrentUserId(); -// Returns the length of the leading root component of a path: "/" on UNIX, -// "C:\" or "\\" (UNC) on Windows. Returns 0 if the path is relative. Like -// Basename() and Dirname(), both '/' and '\' are recognized on all platforms. -// Stripping the returned prefix turns the path into a relative one. -// Examples: -// PathRootPrefixLength("/usr/bin/ls") => 1 -// PathRootPrefixLength("//usr/bin") => 2 -// PathRootPrefixLength("C:\\Windows") => 3 -// PathRootPrefixLength("C:foo") => 0 (relative to the current dir of drive -// C:) PathRootPrefixLength("foo/bar") => 0 -size_t PathRootPrefixLength(const std::string& path); +// Forks the process. +// Parent: calls |parent_cb| with the child's PID and exits with its return +// value. The callback owns any startup output (e.g. printing the PID). +// Child: redirects stdio onto /dev/null, chdirs into / and returns. +void Daemonize(std::function parent_cb); -// Returns true if the path starts with a root component. See -// PathRootPrefixLength(). -bool IsAbsolutePath(const std::string& path); +// Returns the path of the current executable, e.g. /foo/bar/exe. +std::string GetCurExecutablePath(); -// Puts the path to all files under |dir_path| in |output|, recursively walking -// subdirectories. File paths are relative to |dir_path|. Only files are -// included, not directories. Path separator is always '/', even on windows (not -// '\'). -base::Status ListFilesRecursive(const std::string& dir_path, - std::vector& output); +// Returns the directory where the current executable lives in, e.g. /foo/bar. +// This is independent of cwd(). +std::string GetCurExecutableDir(); -// Lists immediate subdirectories in |dir_path| (non-recursive). Directory names -// are relative to |dir_path| and do not include the path separator. Returns -// only directories, not files. Works on both Unix and Windows. -base::Status ListDirectories(const std::string& dir_path, - std::vector& output); +// Memory returned by AlignedAlloc() must be freed via AlignedFree() not just +// free. It makes a difference on Windows where _aligned_malloc() and +// _aligned_free() must be paired. +// Prefer using the AlignedAllocTyped() below which takes care of the pairing. +void* AlignedAlloc(size_t alignment, size_t size); +void AlignedFree(void*); -// Sets |path|'s owner group to |group_name| and permission mode bits to -// |mode_bits|. -base::Status SetFilePermissions(const std::string& path, - const std::string& group_name, - const std::string& mode_bits); +// Detects Sync-mode MTE (currently being tested in some Android builds). +// This is known to use extra memory for the stack history buffer. +bool IsSyncMemoryTaggingEnabled(); -// Returns the size of the file located at |path|, or nullopt in case of error. -std::optional GetFileSize(const std::string& path); +// A RAII version of the above, which takes care of pairing Aligned{Alloc,Free}. +template +struct AlignedDeleter { + inline void operator()(T* ptr) const { AlignedFree(ptr); } +}; -// Returns the size of the open file |fd|, or nullopt in case of error. -std::optional GetFileSize(PlatformHandle fd); +// The remove_extent here and below is to allow defining unique_ptr. +// As per https://en.cppreference.com/w/cpp/memory/unique_ptr the Deleter takes +// always a T*, not a T[]*. +template +using AlignedUniquePtr = + std::unique_ptr::type>>; -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) -// On Windows PlatformHandle is a HANDLE, not a file descriptor. This overload -// accepts the CRT file descriptors returned by base::OpenFile(). On other -// platforms PlatformHandle is an int, so the overload above already covers it. -std::optional GetFileSize(int fd); -#endif +template +AlignedUniquePtr AlignedAllocTyped(size_t n_membs) { + using TU = typename std::remove_extent::type; + return AlignedUniquePtr( + static_cast(AlignedAlloc(alignof(TU), sizeof(TU) * n_membs))); +} -// This class uses inotify (on Linux/Android) to watch for the creation of -// files in the filesystem. When the specified file is created, it triggers a -// callback function. -// Destroying the returned unique_ptr will automatically unregister the watch. -// -// Note: This only works with filesystem paths (not abstract sockets or other -// special file types). -// It's only supported on Linux and Android, it's a no-op (returns nullptr) on -// other platforms. -// -// Usage: -// auto watch = LinuxFileWatch::WatchFileCreation( -// task_runner, "/tmp/my_file", []() { -// // Called when /tmp/my_file is created -// }); -class LinuxFileWatch { +// A RAII wrapper to invoke a function when leaving a function/scope. +template +class OnScopeExitWrapper { public: - // Creates a watcher for file creation. Returns nullptr if the path is not a - // valid filesystem path or if the platform doesn't support inotify. The - // callback will be invoked on the provided TaskRunner when the file is - // created. - static std::unique_ptr WatchFileCreation( - TaskRunner*, - const char* path, - std::function callback); - - virtual ~LinuxFileWatch(); + explicit OnScopeExitWrapper(Func f) : f_(std::move(f)), active_(true) {} + OnScopeExitWrapper(OnScopeExitWrapper&& other) noexcept + : f_(std::move(other.f_)), active_(other.active_) { + other.active_ = false; + } + ~OnScopeExitWrapper() { + if (active_) + f_(); + } - protected: - LinuxFileWatch() = default; + private: + Func f_; + bool active_; }; +template +PERFETTO_WARN_UNUSED_RESULT OnScopeExitWrapper OnScopeExit(Func f) { + return OnScopeExitWrapper(std::move(f)); +} + +// Returns a xxd-style hex dump (hex + ascii chars) of the input data. +std::string HexDump(const void* data, size_t len, size_t bytes_per_line = 16); +inline std::string HexDump(const std::string& data, + size_t bytes_per_line = 16) { + return HexDump(data.data(), data.size(), bytes_per_line); +} + } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_FILE_UTILS_H_ -// gen_amalgamated begin header: include/perfetto/ext/base/string_splitter.h +#endif // INCLUDE_PERFETTO_EXT_BASE_UTILS_H_ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2324,91 +2481,298 @@ class LinuxFileWatch { * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ -#define INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ +#define INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ +#include #include +// gen_amalgamated expanded: #include "perfetto/ext/base/string_view.h" +// gen_amalgamated expanded: #include "perfetto/ext/base/utils.h" // For ssize_t. + namespace perfetto { namespace base { -// C++ version of strtok(). Splits a string without making copies or any heap -// allocations. Destructs the original string passed in input. -// Supports the special case of using \0 as a delimiter. -// The token returned in output are valid as long as the input string is valid. -class StringSplitter { - public: - // Whether an empty string (two delimiters side-to-side) is a valid token. - enum class EmptyTokenMode { - DISALLOW_EMPTY_TOKENS, - ALLOW_EMPTY_TOKENS, +// Returns the length of the destination string (included '=' padding). +// Does NOT include the size of the string null terminator. +inline size_t Base64EncSize(size_t src_size) { + return (src_size + 2) / 3 * 4; +} - DEFAULT = DISALLOW_EMPTY_TOKENS, - }; +// Returns the upper bound on the length of the destination buffer. +// The actual decoded length might be <= the number returned here. +inline size_t Base64DecSize(size_t src_size) { + return (src_size + 3) / 4 * 3; +} - // Can take ownership of the string if passed via std::move(), e.g.: - // StringSplitter(std::move(str), '\n'); - StringSplitter(std::string, - char delimiter, - EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); +// Does NOT null-terminate |dst|. +ssize_t Base64Encode(const void* src, + size_t src_size, + char* dst, + size_t dst_size); - // Splits a C-string. The input string will be forcefully null-terminated (so - // str[size - 1] should be == '\0' or the last char will be truncated). - StringSplitter(char* str, - size_t size, - char delimiter, - EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); +std::string Base64Encode(const void* src, size_t src_size); - // Splits the current token from an outer StringSplitter instance. This is to - // chain splitters as follows: - // for (base::StringSplitter lines(x, '\n'); ss.Next();) - // for (base::StringSplitter words(&lines, ' '); words.Next();) - StringSplitter(StringSplitter*, - char delimiter, - EmptyTokenMode empty_token_mode = EmptyTokenMode::DEFAULT); +inline std::string Base64Encode(StringView sv) { + return Base64Encode(sv.data(), sv.size()); +} - // Returns true if a token is found (in which case it will be stored in - // cur_token()), false if no more tokens are found. - bool Next(); +// Returns -1 in case of failure. +ssize_t Base64Decode(const char* src, + size_t src_size, + uint8_t* dst, + size_t dst_size); - // Returns the next token if found (in which case it will be stored in - // cur_token()), nullptr if no more tokens are found. - char* NextToken() { return Next() ? cur_token() : nullptr; } +std::optional Base64Decode(const char* src, size_t src_size); - // Returns the current token iff last call to Next() returned true. In this - // case it guarantees that the returned string is always null terminated. - // In all other cases (before the 1st call to Next() and after Next() returns - // false) returns nullptr. - char* cur_token() { return cur_; } +inline std::optional Base64Decode(StringView sv) { + return Base64Decode(sv.data(), sv.size()); +} - // Returns the length of the current token (excluding the null terminator). - size_t cur_token_size() const { return cur_size_; } +} // namespace base +} // namespace perfetto - // Return the untokenized remainder of the input string that occurs after the - // current token. - char* remainder() { return next_; } +#endif // INCLUDE_PERFETTO_EXT_BASE_BASE64_H_ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - // Returns the size of the untokenized input - size_t remainder_size() { return static_cast(end_ - next_); } +// gen_amalgamated expanded: #include "perfetto/ext/base/base64.h" - private: - StringSplitter(const StringSplitter&) = delete; - StringSplitter& operator=(const StringSplitter&) = delete; - void Initialize(char* str, size_t size); +namespace perfetto { +namespace base { - std::string str_; - char* cur_; - size_t cur_size_; - char* next_; - char* end_; // STL-style, points one past the last char. - const char delimiter_; - const EmptyTokenMode empty_token_mode_; +namespace { + +constexpr char kPadding = '='; + +constexpr char kEncTable[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static_assert(sizeof(kEncTable) == (1u << 6) + sizeof('\0'), "Bad table size"); + +// Maps an ASCII character to its 6-bit value. It only contains translations +// from '+' to 'z'. Supports the standard (+/) and URL-safe (-_) alphabets. +constexpr uint8_t kX = 0xff; // Value used for invalid characters +constexpr uint8_t kDecTable[] = { + 62, kX, 62, kX, 63, 52, 53, 54, 55, 56, // 00 - 09 + 57, 58, 59, 60, 61, kX, kX, kX, 0, kX, // 10 - 19 + kX, kX, 0, 1, 2, 3, 4, 5, 6, 7, // 20 - 29 + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, // 30 - 39 + 18, 19, 20, 21, 22, 23, 24, 25, kX, kX, // 40 - 49 + kX, kX, 63, kX, 26, 27, 28, 29, 30, 31, // 50 - 59 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, // 60 - 69 + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 70 - 79 +}; +constexpr char kMinDecChar = '+'; +constexpr char kMaxDecChar = 'z'; +static_assert(kMaxDecChar - kMinDecChar <= sizeof(kDecTable), "Bad table size"); + +inline uint8_t DecodeChar(char c) { + if (c < kMinDecChar || c > kMaxDecChar) + return kX; + return kDecTable[c - kMinDecChar]; +} + +} // namespace + +ssize_t Base64Encode(const void* src, + size_t src_size, + char* dst, + size_t dst_size) { + const size_t padded_dst_size = Base64EncSize(src_size); + if (dst_size < padded_dst_size) + return -1; // Not enough space in output. + + const uint8_t* rd = static_cast(src); + const uint8_t* const end = rd + src_size; + size_t wr_size = 0; + while (rd < end) { + uint8_t s[3]{}; + s[0] = *(rd++); + dst[wr_size++] = kEncTable[s[0] >> 2]; + + uint8_t carry0 = static_cast((s[0] & 0x03) << 4); + if (PERFETTO_LIKELY(rd < end)) { + s[1] = *(rd++); + dst[wr_size++] = kEncTable[carry0 | (s[1] >> 4)]; + } else { + dst[wr_size++] = kEncTable[carry0]; + dst[wr_size++] = kPadding; + dst[wr_size++] = kPadding; + break; + } + + uint8_t carry1 = static_cast((s[1] & 0x0f) << 2); + if (PERFETTO_LIKELY(rd < end)) { + s[2] = *(rd++); + dst[wr_size++] = kEncTable[carry1 | (s[2] >> 6)]; + } else { + dst[wr_size++] = kEncTable[carry1]; + dst[wr_size++] = kPadding; + break; + } + + dst[wr_size++] = kEncTable[s[2] & 0x3f]; + } + PERFETTO_DCHECK(wr_size == padded_dst_size); + return static_cast(padded_dst_size); +} + +std::string Base64Encode(const void* src, size_t src_size) { + std::string dst; + dst.resize(Base64EncSize(src_size)); + auto res = Base64Encode(src, src_size, &dst[0], dst.size()); + PERFETTO_CHECK(res == static_cast(dst.size())); + return dst; +} + +ssize_t Base64Decode(const char* src, + size_t src_size, + uint8_t* dst, + size_t dst_size) { + const size_t min_dst_size = Base64DecSize(src_size); + if (dst_size < min_dst_size) + return -1; + + const char* rd = src; + const char* const end = src + src_size; + size_t wr_size = 0; + + char s[4]{}; + while (rd < end) { + uint8_t d[4]; + for (uint32_t j = 0; j < 4; j++) { + // Padding is only feasible for the last 2 chars of each group of 4. + s[j] = rd < end ? *(rd++) : (j < 2 ? '\0' : kPadding); + d[j] = DecodeChar(s[j]); + if (d[j] == kX) + return -1; // Invalid input char. + } + dst[wr_size] = static_cast((d[0] << 2) | (d[1] >> 4)); + dst[wr_size + 1] = static_cast((d[1] << 4) | (d[2] >> 2)); + dst[wr_size + 2] = static_cast((d[2] << 6) | (d[3])); + wr_size += 3; + } + + PERFETTO_CHECK(wr_size <= dst_size); + wr_size -= (s[3] == kPadding ? 1 : 0) + (s[2] == kPadding ? 1 : 0); + return static_cast(wr_size); +} + +std::optional Base64Decode(const char* src, size_t src_size) { + std::string dst; + dst.resize(Base64DecSize(src_size)); + auto res = Base64Decode(src, src_size, reinterpret_cast(&dst[0]), + dst.size()); + if (res < 0) + return std::nullopt; // Decoding error. + + PERFETTO_CHECK(res <= static_cast(dst.size())); + dst.resize(static_cast(res)); + return std::make_optional(dst); +} + +} // namespace base +} // namespace perfetto +// gen_amalgamated begin source: src/base/cpu_info.cc +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info.h +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ + +#include +#include +#include +#include + +namespace perfetto { +namespace base { + +struct CpuInfo { + std::string processor; + uint32_t cpu_index = 0; + std::optional implementer; + std::optional architecture; + std::optional variant; + std::optional part; + std::optional revision; + uint64_t features = 0; + char arm_cpuid[32] = {}; }; +// Parses the contents of the input string into per-CPU entries. +std::vector ParseCpuInfo(std::string proc_cpu_info); + +// Reads /proc/cpuinfo and parses it into per-CPU entries. +std::vector ReadCpuInfo(); + } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_STRING_SPLITTER_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/cpu_info_features_allowlist.h +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ +#define INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ + +namespace perfetto { +namespace base { + +// APPEND ONLY. DO NOT EVER REMOVE ENTRIES FROM THIS ARRAY OR REORDER. +// This array is used both by traced_probes and trace_processor to index the +// cpuinfo flags. Changing the order will break trace_processor compatibility +// with old traces. +constexpr const char* kCpuInfoFeatures[] = { + "mte", // DO NOT REMOVE/REODER. + "mte3", // DO NOT REMOVE/REODER. +}; + +} // namespace base +} // namespace perfetto + +#endif // INCLUDE_PERFETTO_EXT_BASE_CPU_INFO_FEATURES_ALLOWLIST_H_ /* * Copyright (C) 2026 The Android Open Source Project * @@ -3991,6 +4355,51 @@ bool FlushFile(int fd) { #endif } +ScopedFile DupFile(int fd) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + ScopedFile duplicate(_dup(fd)); + if (duplicate && !SetHandleInformation( + reinterpret_cast(_get_osfhandle(*duplicate)), + HANDLE_FLAG_INHERIT, 0)) + return ScopedFile(); + return duplicate; +#else + return ScopedFile(fcntl(fd, F_DUPFD_CLOEXEC, 0)); +#endif +} + +bool IsRegularFile(const std::string& path) { + struct stat st{}; + if (stat(path.c_str(), &st) != 0) + return false; + PERFETTO_MSAN_UNPOISON(&st, sizeof(st)); + return (st.st_mode & S_IFMT) == S_IFREG; +} + +bool IsSameFile(const std::string& first, const std::string& second) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + auto identity = [](const std::string& path, + BY_HANDLE_FILE_INFORMATION* info) { + ScopedPlatformHandle file(CreateFileA( + path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + return file && GetFileInformationByHandle(*file, info); + }; + BY_HANDLE_FILE_INFORMATION a{}, b{}; + return identity(first, &a) && identity(second, &b) && + a.dwVolumeSerialNumber == b.dwVolumeSerialNumber && + a.nFileIndexHigh == b.nFileIndexHigh && + a.nFileIndexLow == b.nFileIndexLow; +#else + struct stat a{}, b{}; + if (stat(first.c_str(), &a) != 0 || stat(second.c_str(), &b) != 0) + return false; + PERFETTO_MSAN_UNPOISON(&a, sizeof(a)); + PERFETTO_MSAN_UNPOISON(&b, sizeof(b)); + return a.st_dev == b.st_dev && a.st_ino == b.st_ino; +#endif +} + bool SeekFile(int fd, uint64_t offset) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) if (fd < 0) { @@ -4082,15 +4491,6 @@ ScopedFile OpenFile(const std::string& path, int flags, FileOpenMode mode) { ScopedFstream OpenFstream(const std::string& path, const std::string& mode) { ScopedFstream file; - // On Windows fopen interprets filename using the ANSI or OEM codepage but - // sqlite3_value_text returns a UTF-8 string. To make sure we interpret the - // filename correctly we use _wfopen and a UTF-16 string on windows. - // - // On Windows fopen also open files in the text mode by default, but we want - // to open them in the binary mode, to avoid silly EOL translations (and to be - // consistent with base::OpenFile). So we check the mode first and append 'b' - // mode only when it makes sense. -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) std::string s_mode(mode); // Windows supports non-standard mode extension that sets encoding in text // mode. If you need to open a FILE* in text mode, use the fopen API directly. @@ -4100,13 +4500,22 @@ ScopedFstream OpenFstream(const std::string& path, const std::string& mode) { if (!is_binary_mode) s_mode += 'b'; +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + // On Windows fopen interprets filename using the ANSI or OEM codepage but + // sqlite3_value_text returns a UTF-8 string. To make sure we interpret the + // filename correctly we use _wfopen and a UTF-16 string on windows. + // + // On Windows fopen also open files in the text mode by default, but we want + // to open them in the binary mode, to avoid silly EOL translations (and to be + // consistent with base::OpenFile). So we check the mode first and append 'b' + // mode only when it makes sense. auto w_path = ToUtf16(path); auto w_mode = ToUtf16(s_mode); if (w_path && w_mode) { file.reset(_wfopen(w_path->c_str(), w_mode->c_str())); } #else - file.reset(fopen(path.c_str(), mode.c_str())); + file.reset(fopen(path.c_str(), s_mode.c_str())); #endif return file; } @@ -4569,10 +4978,11 @@ LinuxFileWatch::~LinuxFileWatch() = default; } // namespace base } // namespace perfetto -// gen_amalgamated begin source: src/base/getopt_compat.cc -// gen_amalgamated begin header: include/perfetto/ext/base/getopt_compat.h +// gen_amalgamated begin source: src/base/flat_hash_map.cc +// gen_amalgamated begin header: include/perfetto/ext/base/flat_hash_map.h +// gen_amalgamated begin header: include/perfetto/ext/base/bits.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -4587,63 +4997,95 @@ LinuxFileWatch::~LinuxFileWatch() = default; * limitations under the License. */ -#ifndef INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_ -#define INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_ +#ifndef INCLUDE_PERFETTO_EXT_BASE_BITS_H_ +#define INCLUDE_PERFETTO_EXT_BASE_BITS_H_ -#include // For std::nullptr_t +#include +#include -// No translation units other than base/getopt.h and getopt_compat_unittest.cc -// should directly include this file. Use base/getopt.h instead. +// gen_amalgamated expanded: #include "perfetto/base/build_config.h" +// gen_amalgamated expanded: #include "perfetto/base/compiler.h" + +#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT) +#include +#endif namespace perfetto { namespace base { -namespace getopt_compat { -// A tiny getopt() replacement for Windows, which doesn't have . -// This implementation is based on the subset of features that we use in the -// Perfetto codebase. It doesn't even try to deal with the full surface of GNU's -// getopt(). -// Limitations: -// - getopt_long_only() is not supported. -// - optional_argument is not supported. That is extremely subtle and caused us -// problems in the past with GNU's getopt. -// - It does not reorder non-option arguments. It behaves like MacOS getopt, or -// GNU's when POSIXLY_CORRECT=1. -// - Doesn't expose optopt or opterr. -// - option.flag and longindex are not supported and must be nullptr. +inline PERFETTO_ALWAYS_INLINE uint32_t CountLeadZeros32(uint32_t value) { +#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT) + return static_cast(_lzcnt_u32(value)); +#elif defined(__GNUC__) || defined(__clang__) + return value ? static_cast(__builtin_clz(value)) : 32u; +#else + unsigned long out; + return _BitScanReverse(&out, value) ? 31 - out : 32u; +#endif +} -enum { - no_argument = 0, - required_argument = 1, -}; +inline PERFETTO_ALWAYS_INLINE uint32_t CountLeadZeros64(uint64_t value) { +#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT) + return static_cast(_lzcnt_u64(value)); +#elif defined(__GNUC__) || defined(__clang__) + return value ? static_cast(__builtin_clzll(value)) : 64u; +#else + unsigned long out; + return _BitScanReverse64(&out, value) ? 63 - out : 64u; +#endif +} -struct option { - const char* name; - int has_arg; - std::nullptr_t flag; // Only nullptr is supported. - int val; -}; +template +uint32_t CountLeadZeros(T value) { + if constexpr (sizeof(T) == 8) + return CountLeadZeros64(static_cast(value)); + return CountLeadZeros32(static_cast(value)); +} -extern char* optarg; -extern int optind; -extern int optopt; -extern int opterr; +inline PERFETTO_ALWAYS_INLINE uint32_t CountTrailZeros64(uint64_t value) { +#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT) + return static_cast(_tzcnt_u64(value)); +#elif defined(__GNUC__) || defined(__clang__) + return value ? static_cast(__builtin_ctzll(value)) : 64u; +#else + unsigned long out; + return _BitScanForward64(&out, value) ? static_cast(out) : 64u; +#endif +} -int getopt_long(int argc, - char** argv, - const char* shortopts, - const option* longopts, - std::nullptr_t /*longindex is not supported*/); +inline PERFETTO_ALWAYS_INLINE uint32_t CountTrailZeros32(uint32_t value) { +#if PERFETTO_BUILDFLAG(PERFETTO_X64_CPU_OPT) + return _tzcnt_u32(value); +#elif defined(__GNUC__) || defined(__clang__) + return value ? static_cast(__builtin_ctz(value)) : 32u; +#else + unsigned long out; + return _BitScanForward(&out, value) ? out : 32u; +#endif +} -int getopt(int argc, char** argv, const char* shortopts); +template +inline PERFETTO_ALWAYS_INLINE uint32_t CountTrailZeros(T value) { + if (sizeof(T) == 8) { + return CountTrailZeros64(static_cast(value)); + } + return CountTrailZeros32(static_cast(value)); +} + +template +constexpr bool AllBitsSet(T v) { + return v == static_cast(-1); +} -} // namespace getopt_compat } // namespace base } // namespace perfetto -#endif // INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_ +#endif // INCLUDE_PERFETTO_EXT_BASE_BITS_H_ +// gen_amalgamated begin header: include/perfetto/ext/base/flat_hash_map_v1.h +// gen_amalgamated begin header: include/perfetto/ext/base/murmur_hash.h +// gen_amalgamated begin header: include/perfetto/ext/base/hash.h /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -4658,289 +5100,90 @@ int getopt(int argc, char** argv, const char* shortopts); * limitations under the License. */ -// gen_amalgamated expanded: #include "perfetto/ext/base/getopt_compat.h" - -#include -#include -#include - -#include - -// gen_amalgamated expanded: #include "perfetto/base/logging.h" +#ifndef INCLUDE_PERFETTO_EXT_BASE_HASH_H_ +#define INCLUDE_PERFETTO_EXT_BASE_HASH_H_ -namespace perfetto { -namespace base { -namespace getopt_compat { +#include +#include +#include +#include +#include +#include -char* optarg = nullptr; -int optind = 0; -int optopt = 0; -int opterr = 1; +namespace perfetto::base { -namespace { +// ============================================================================ +// Absl-style hash customization point +// ============================================================================ +// +// To make a type hashable with Perfetto hash functions, define a friend +// function template: +// +// template +// friend H PerfettoHashValue(H h, const MyType& value) { +// return H::Combine(std::move(h), value.field1, value.field2); +// } +// +// This function will be found via ADL (Argument Dependent Lookup) when hashing +// your type. No forward declaration is needed - ADL finds it in your type's +// namespace. -char* nextchar = nullptr; +// ============================================================================ +// Built-in PerfettoHashValue implementations for common standard library +// types. These allow standard library types to work seamlessly with the +// absl-style hash API without requiring users to define their own +// implementations. +// ============================================================================ -const option* LookupLongOpt(const std::vector