Skip to content

JSOM 2.0.0 — RFC 8259 conformance, bounded recursion, one parser

Choose a tag to compare

@HarryPehkonen HarryPehkonen released this 17 Sep 18:13
· 31 commits to main since this release

JSOM 2.0.0 — every input is safe, the spec is enforced, one parser.

Breaking changes

  • Recursion is bounded. Documents nested deeper than 256 levels are rejected with
    Maximum nesting depth exceeded (limit 256) instead of exhausting the C++ stack. The
    limit is JsonParseOptions::max_depth (default limits::MAX_NESTING_DEPTH = 256) and
    every traversal honours it: parsing, serialization (compact and pretty), comparison,
    path listing and formatting.
  • The RFC 8259 lexical rules are enforced on every parse, whatever the options:
    a raw control character (U+0000–U+001F) inside a string, \u without exactly four hex
    digits, any escape outside " \ / b f n r t u, and formfeed/vertical tab used as
    whitespace are all syntax errors. Input that previously parsed leniently may now be
    rejected — that is the point.
  • The event-based streaming API is gone: StreamingParser, ParseEvents, PathNode,
    DocumentBuilder and parse_document_streaming(). parse_document() is the single
    entry point (include/jsom/parse_document.hpp).

Security

  • Unbounded recursion (CWE-674) fixed. ~60 KB of nested [ was enough to kill a
    process with SIGSEGV — not an exception, so nothing could catch it. That is a remote
    denial of service for anything parsing JSON from off-machine, and on a 1 MB
    worker-thread stack about 3 KB sufficed. Now rejected, with the limit configurable so
    callers on small stacks can lower it.

Conformance

  • The nst/JSONTestSuite corpus is vendored and runnable in-tree
    (cmake --build build --target run_conformance), parsing each file in a forked child
    so that a crash is a result rather than a lost run.
  • y_ 95/95 must-accept · n_ 188/188 must-reject · 0 crashes with
    --validation=numbers. By default n_ is 162/188, because the number grammar stays
    lenient unless asked.
  • Reference point: nlohmann/json 3.11.3 scores y_ 95/95, n_ 187/188 on the same
    corpus.

Added

  • JsonParseOptions::max_depth for per-parse resource limits.
  • Opt-in number-grammar validation: JsonParseOptions::validate_numbers,
    ParsePresets::Validate, and --validation=lazy|numbers on jsom validate and
    jsom format.
  • tools/nesting_probe.cpp, tools/perf_probe.cpp — re-runnable measurement probes, so
    the numbers in OPTIMIZATIONS.md can be reproduced.
  • Single-source versioning: project(JSOM VERSION ...) generates <jsom/version.hpp>
    (JSOM_VERSION, JSOM_VERSION_MAJOR/MINOR/PATCH); nothing else hard-codes a version.

Performance

  • Faster than nlohmann/json 3.11.3 in all 17 paired benchmarks (1.02×–1.98×).
  • Cost of the always-on lexical rules: +1.5% on string-heavy parsing, unmeasurable
    elsewhere. Number validation: +6% (short numbers), +12% (17-digit), +0.5% (mixed) —
    which is why it is opt-in.

Testing

  • 185 tests (unit + regression), plus the same suite under ASan+UBSan, plus a fuzz target
    that drives the parser in both configurations on every input and asserts
    parse(to_json(doc)) == doc.
  • jsom version reports the version; CODING_STANDARDS.md defines the gates
    (zero warnings, tests, sanitizers, fuzzing, measured performance).