diff --git a/.clang-format b/.clang-format index 2a80669..b7cf997 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ BasedOnStyle: LLVM DerivePointerAlignment: false PointerAlignment: Left - +SpacesBeforeTrailingComments: 1 diff --git a/.clang-tidy b/.clang-tidy index be3d06a..99e914d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,7 +4,7 @@ WarningsAsErrors: '' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false FormatStyle: none -CheckOptions: +CheckOptions: - key: modernize-use-using.IgnoreMacros value: '0' ... diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..221f8b8 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,20 @@ +name: clang-format check +on: [check_run, pull_request, push] + +jobs: + formatting-check: + name: formatting check + runs-on: ubuntu-latest + strategy: + matrix: + path: + - 'src' + - 'examples' + - 'include' + steps: + - uses: actions/checkout@v4 + - name: runs clang-format style check for C/C++/Protobuf programs. + uses: jidicula/clang-format-action@v4.13.0 + with: + clang-format-version: '18' + check-path: ${{ matrix.path }} diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..91f387a --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,18 @@ +name: cmake +on: [check_run, push, pull_request] +jobs: + cmake-publish: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: checkout project + uses: actions/checkout@v4 + + - name: build project + uses: threeal/cmake-action@v2.0.0 + diff --git a/.github/workflows/meson.yml b/.github/workflows/meson.yml new file mode 100644 index 0000000..22fe32f --- /dev/null +++ b/.github/workflows/meson.yml @@ -0,0 +1,65 @@ +name: meson build and test +run-name: update pushed to ${{ github.ref }} +on: [check_run, push, pull_request] + +jobs: + meson-publish: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup python + uses: actions/setup-python@v5 + + - name: meson build + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + action: build + + - name: meson test + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + action: test + + meson-coverage: + runs-on: ubuntu-latest + + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup python + uses: actions/setup-python@v5 + + - name: meson build + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + setup-options: -Db_coverage=true + action: build + + - name: meson test + uses: BSFishy/meson-build@v1.0.3 + with: + meson-version: 1.5.1 + ninja-version: 1.11.1.1 + setup-options: -Db_coverage=true + action: test + + - name: generate code coverage report + uses: threeal/gcovr-action@v1.0.0 + with: + coveralls-send: true + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 68f40b0..9682782 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ /libs/ /doc/doxyfile /dist/ +/.cache/ # MSVC project files: *.sln @@ -30,6 +31,7 @@ CMakeFiles/ /pkg-config/jsoncpp.pc jsoncpp_lib_static.dir/ +compile_commands.json # In case someone runs cmake in the root-dir: /CMakeCache.txt @@ -50,3 +52,6 @@ jsoncpp_lib_static.dir/ # DS_Store .DS_Store + +# temps +/version diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6d7ccde..0000000 --- a/.travis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Build matrix / environment variables are explained on: -# http://about.travis-ci.org/docs/user/build-configuration/ -# This file can be validated on: http://www.yamllint.com/ -# Or using the Ruby based travel command line tool: -# gem install travis --no-rdoc --no-ri -# travis lint .travis.yml -language: cpp -sudo: false -addons: - homebrew: - packages: - - clang-format - - meson - - ninja - update: false # do not update homebrew by default - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-8 - packages: - - clang-format-8 - - clang-8 - - valgrind -matrix: - include: - - name: Mac clang meson static release testing - os: osx - osx_image: xcode11 - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial clang meson static release testing - os: linux - dist: xenial - compiler: clang - env: - CXX="clang++" - CC="clang" - LIB_TYPE=static - BUILD_TYPE=release - PYTHONUSERBASE="$(pwd)/LOCAL" - PATH="$PYTHONUSERBASE/bin:$PATH" - # before_install and install steps only needed for linux meson builds - before_install: - - source ./.travis_scripts/travis.before_install.${TRAVIS_OS_NAME}.sh - install: - - source ./.travis_scripts/travis.install.${TRAVIS_OS_NAME}.sh - script: ./.travis_scripts/meson_builder.sh - - name: Linux xenial gcc cmake coverage - os: linux - dist: xenial - compiler: gcc - env: - CXX=g++ - CC=gcc - DO_Coverage=ON - BUILD_TOOL="Unix Makefiles" - BUILD_TYPE=Debug - LIB_TYPE=shared - DESTDIR=/tmp/cmake_json_cpp - before_install: - - pip install --user cpp-coveralls - script: ./.travis_scripts/cmake_builder.sh - after_success: - - coveralls --include src/lib_json --include include -notifications: - email: false diff --git a/.travis_scripts/cmake_builder.sh b/.travis_scripts/cmake_builder.sh deleted file mode 100755 index f3d4e46..0000000 --- a/.travis_scripts/cmake_builder.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env sh -# This script can be used on the command line directly to configure several -# different build environments. -# This is called by `.travis.yml` via Travis CI. -# Travis supplies $TRAVIS_OS_NAME. -# http://docs.travis-ci.com/user/multi-os/ -# Our .travis.yml also defines: - -# - BUILD_TYPE=Release/Debug -# - LIB_TYPE=static/shared -# -# Optional environmental variables -# - DESTDIR <- used for setting the install prefix -# - BUILD_TOOL=["Unix Makefile"|"Ninja"] -# - BUILDNAME <- how to identify this build on the dashboard -# - DO_MemCheck <- if set, try to use valgrind -# - DO_Coverage <- if set, try to do dashboard coverage testing -# - -env_set=1 -if ${BUILD_TYPE+false}; then - echo "BUILD_TYPE not set in environment." - env_set=0 -fi -if ${LIB_TYPE+false}; then - echo "LIB_TYPE not set in environment." - env_set=0 -fi -if ${CXX+false}; then - echo "CXX not set in environment." - env_set=0 -fi - - -if [ ${env_set} -eq 0 ]; then - echo "USAGE: CXX=$(which clang++) BUILD_TYPE=[Release|Debug] LIB_TYPE=[static|shared] $0" - echo "" - echo "Examples:" - echo " CXX=$(which clang++) BUILD_TYPE=Release LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Debug LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Release LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=Debug LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - - echo " CXX=$(which g++) BUILD_TYPE=Release LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Debug LIB_TYPE=shared DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Release LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=Debug LIB_TYPE=static DESTDIR=/tmp/cmake_json_cpp $0" - - exit -1 -fi - -if ${DESTDIR+false}; then - DESTDIR="/usr/local" -fi - -# -e: fail on error -# -v: show commands -# -x: show expanded commands -set -vex - -env | sort - -which cmake -cmake --version - -echo ${CXX} -${CXX} --version -_COMPILER_NAME=`basename ${CXX}` -if [ "${LIB_TYPE}" = "shared" ]; then - _CMAKE_BUILD_SHARED_LIBS=ON -else - _CMAKE_BUILD_SHARED_LIBS=OFF -fi - -CTEST_TESTING_OPTION="-D ExperimentalTest" -# - DO_MemCheck <- if set, try to use valgrind -if ! ${DO_MemCheck+false}; then - valgrind --version - CTEST_TESTING_OPTION="-D ExperimentalMemCheck" -else -# - DO_Coverage <- if set, try to do dashboard coverage testing - if ! ${DO_Coverage+false}; then - export CXXFLAGS="-fprofile-arcs -ftest-coverage" - export LDFLAGS="-fprofile-arcs -ftest-coverage" - CTEST_TESTING_OPTION="-D ExperimentalTest -D ExperimentalCoverage" - #gcov --version - fi -fi - -# Ninja = Generates build.ninja files. -if ${BUILD_TOOL+false}; then - BUILD_TOOL="Ninja" - export _BUILD_EXE=ninja - which ninja - ninja --version -else -# Unix Makefiles = Generates standard UNIX makefiles. - export _BUILD_EXE=make -fi - -_BUILD_DIR_NAME="build-cmake_${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}_${_BUILD_EXE}" -mkdir -p ${_BUILD_DIR_NAME} -cd "${_BUILD_DIR_NAME}" - if ${BUILDNAME+false}; then - _HOSTNAME=`hostname -s` - BUILDNAME="${_HOSTNAME}_${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}_${_BUILD_EXE}" - fi - cmake \ - -G "${BUILD_TOOL}" \ - -DBUILDNAME:STRING="${BUILDNAME}" \ - -DCMAKE_CXX_COMPILER:PATH=${CXX} \ - -DCMAKE_BUILD_TYPE:STRING=${BUILD_TYPE} \ - -DBUILD_SHARED_LIBS:BOOL=${_CMAKE_BUILD_SHARED_LIBS} \ - -DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR} \ - ../ - - ctest -C ${BUILD_TYPE} -D ExperimentalStart -D ExperimentalConfigure -D ExperimentalBuild ${CTEST_TESTING_OPTION} -D ExperimentalSubmit - # Final step is to verify that installation succeeds - cmake --build . --config ${BUILD_TYPE} --target install - - if [ "${DESTDIR}" != "/usr/local" ]; then - ${_BUILD_EXE} install - fi -cd - - -if ${CLEANUP+false}; then - echo "Skipping cleanup: build directory will persist." -else - rm -r "${_BUILD_DIR_NAME}" -fi diff --git a/.travis_scripts/meson_builder.sh b/.travis_scripts/meson_builder.sh deleted file mode 100755 index 1fdd8f6..0000000 --- a/.travis_scripts/meson_builder.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env sh -# This script can be used on the command line directly to configure several -# different build environments. -# This is called by `.travis.yml` via Travis CI. -# Travis supplies $TRAVIS_OS_NAME. -# http://docs.travis-ci.com/user/multi-os/ -# Our .travis.yml also defines: - -# - BUILD_TYPE=release/debug -# - LIB_TYPE=static/shared - -env_set=1 -if ${BUILD_TYPE+false}; then - echo "BUILD_TYPE not set in environment." - env_set=0 -fi -if ${LIB_TYPE+false}; then - echo "LIB_TYPE not set in environment." - env_set=0 -fi -if ${CXX+false}; then - echo "CXX not set in environment." - env_set=0 -fi - - -if [ ${env_set} -eq 0 ]; then - echo "USAGE: CXX=$(which clang++) BUILD_TYPE=[release|debug] LIB_TYPE=[static|shared] $0" - echo "" - echo "Examples:" - echo " CXX=$(which clang++) BUILD_TYPE=release LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=debug LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=release LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which clang++) BUILD_TYPE=debug LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - - echo " CXX=$(which g++) BUILD_TYPE=release LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=debug LIB_TYPE=shared DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=release LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - echo " CXX=$(which g++) BUILD_TYPE=debug LIB_TYPE=static DESTDIR=/tmp/meson_json_cpp $0" - - exit -1 -fi - -if ${DESTDIR+false}; then - DESTDIR="/usr/local" -fi - -# -e: fail on error -# -v: show commands -# -x: show expanded commands -set -vex - - -env | sort - -which python3 -which meson -which ninja -echo ${CXX} -${CXX} --version -python3 --version -meson --version -ninja --version -_COMPILER_NAME=`basename ${CXX}` -_BUILD_DIR_NAME="build-${BUILD_TYPE}_${LIB_TYPE}_${_COMPILER_NAME}" - -./.travis_scripts/run-clang-format.sh -meson --fatal-meson-warnings --werror --buildtype ${BUILD_TYPE} --default-library ${LIB_TYPE} . "${_BUILD_DIR_NAME}" -ninja -v -j 2 -C "${_BUILD_DIR_NAME}" - -cd "${_BUILD_DIR_NAME}" - meson test --no-rebuild --print-errorlogs - - if [ "${DESTDIR}" != "/usr/local" ]; then - ninja install - fi -cd - - -if ${CLEANUP+false}; then - echo "Skipping cleanup: build directory will persist." -else - rm -r "${_BUILD_DIR_NAME}" -fi diff --git a/.travis_scripts/run-clang-format.py b/.travis_scripts/run-clang-format.py deleted file mode 100755 index 605b5aa..0000000 --- a/.travis_scripts/run-clang-format.py +++ /dev/null @@ -1,356 +0,0 @@ -#!/usr/bin/env python -"""A wrapper script around clang-format, suitable for linting multiple files -and to use for continuous integration. -This is an alternative API for the clang-format command line. -It runs over multiple files and directories in parallel. -A diff output is produced and a sensible exit code is returned. - -NOTE: pulled from https://github.com/Sarcasm/run-clang-format, which is -licensed under the MIT license. -""" - -from __future__ import print_function, unicode_literals - -import argparse -import codecs -import difflib -import fnmatch -import io -import multiprocessing -import os -import signal -import subprocess -import sys -import traceback - -from functools import partial - -try: - from subprocess import DEVNULL # py3k -except ImportError: - DEVNULL = open(os.devnull, "wb") - - -DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx' - - -class ExitStatus: - SUCCESS = 0 - DIFF = 1 - TROUBLE = 2 - - -def list_files(files, recursive=False, extensions=None, exclude=None): - if extensions is None: - extensions = [] - if exclude is None: - exclude = [] - - out = [] - for file in files: - if recursive and os.path.isdir(file): - for dirpath, dnames, fnames in os.walk(file): - fpaths = [os.path.join(dirpath, fname) for fname in fnames] - for pattern in exclude: - # os.walk() supports trimming down the dnames list - # by modifying it in-place, - # to avoid unnecessary directory listings. - dnames[:] = [ - x for x in dnames - if - not fnmatch.fnmatch(os.path.join(dirpath, x), pattern) - ] - fpaths = [ - x for x in fpaths if not fnmatch.fnmatch(x, pattern) - ] - for f in fpaths: - ext = os.path.splitext(f)[1][1:] - if ext in extensions: - out.append(f) - else: - out.append(file) - return out - - -def make_diff(file, original, reformatted): - return list( - difflib.unified_diff( - original, - reformatted, - fromfile='{}\t(original)'.format(file), - tofile='{}\t(reformatted)'.format(file), - n=3)) - - -class DiffError(Exception): - def __init__(self, message, errs=None): - super(DiffError, self).__init__(message) - self.errs = errs or [] - - -class UnexpectedError(Exception): - def __init__(self, message, exc=None): - super(UnexpectedError, self).__init__(message) - self.formatted_traceback = traceback.format_exc() - self.exc = exc - - -def run_clang_format_diff_wrapper(args, file): - try: - ret = run_clang_format_diff(args, file) - return ret - except DiffError: - raise - except Exception as e: - raise UnexpectedError('{}: {}: {}'.format(file, e.__class__.__name__, - e), e) - - -def run_clang_format_diff(args, file): - try: - with io.open(file, 'r', encoding='utf-8') as f: - original = f.readlines() - except IOError as exc: - raise DiffError(str(exc)) - invocation = [args.clang_format_executable, file] - - # Use of utf-8 to decode the process output. - # - # Hopefully, this is the correct thing to do. - # - # It's done due to the following assumptions (which may be incorrect): - # - clang-format will returns the bytes read from the files as-is, - # without conversion, and it is already assumed that the files use utf-8. - # - if the diagnostics were internationalized, they would use utf-8: - # > Adding Translations to Clang - # > - # > Not possible yet! - # > Diagnostic strings should be written in UTF-8, - # > the client can translate to the relevant code page if needed. - # > Each translation completely replaces the format string - # > for the diagnostic. - # > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation - # - # It's not pretty, due to Python 2 & 3 compatibility. - encoding_py3 = {} - if sys.version_info[0] >= 3: - encoding_py3['encoding'] = 'utf-8' - - try: - proc = subprocess.Popen( - invocation, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - **encoding_py3) - except OSError as exc: - raise DiffError( - "Command '{}' failed to start: {}".format( - subprocess.list2cmdline(invocation), exc - ) - ) - proc_stdout = proc.stdout - proc_stderr = proc.stderr - if sys.version_info[0] < 3: - # make the pipes compatible with Python 3, - # reading lines should output unicode - encoding = 'utf-8' - proc_stdout = codecs.getreader(encoding)(proc_stdout) - proc_stderr = codecs.getreader(encoding)(proc_stderr) - # hopefully the stderr pipe won't get full and block the process - outs = list(proc_stdout.readlines()) - errs = list(proc_stderr.readlines()) - proc.wait() - if proc.returncode: - raise DiffError( - "Command '{}' returned non-zero exit status {}".format( - subprocess.list2cmdline(invocation), proc.returncode - ), - errs, - ) - return make_diff(file, original, outs), errs - - -def bold_red(s): - return '\x1b[1m\x1b[31m' + s + '\x1b[0m' - - -def colorize(diff_lines): - def bold(s): - return '\x1b[1m' + s + '\x1b[0m' - - def cyan(s): - return '\x1b[36m' + s + '\x1b[0m' - - def green(s): - return '\x1b[32m' + s + '\x1b[0m' - - def red(s): - return '\x1b[31m' + s + '\x1b[0m' - - for line in diff_lines: - if line[:4] in ['--- ', '+++ ']: - yield bold(line) - elif line.startswith('@@ '): - yield cyan(line) - elif line.startswith('+'): - yield green(line) - elif line.startswith('-'): - yield red(line) - else: - yield line - - -def print_diff(diff_lines, use_color): - if use_color: - diff_lines = colorize(diff_lines) - if sys.version_info[0] < 3: - sys.stdout.writelines((l.encode('utf-8') for l in diff_lines)) - else: - sys.stdout.writelines(diff_lines) - - -def print_trouble(prog, message, use_colors): - error_text = 'error:' - if use_colors: - error_text = bold_red(error_text) - print("{}: {} {}".format(prog, error_text, message), file=sys.stderr) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--clang-format-executable', - metavar='EXECUTABLE', - help='path to the clang-format executable', - default='clang-format') - parser.add_argument( - '--extensions', - help='comma separated list of file extensions (default: {})'.format( - DEFAULT_EXTENSIONS), - default=DEFAULT_EXTENSIONS) - parser.add_argument( - '-r', - '--recursive', - action='store_true', - help='run recursively over directories') - parser.add_argument('files', metavar='file', nargs='+') - parser.add_argument( - '-q', - '--quiet', - action='store_true') - parser.add_argument( - '-j', - metavar='N', - type=int, - default=0, - help='run N clang-format jobs in parallel' - ' (default number of cpus + 1)') - parser.add_argument( - '--color', - default='auto', - choices=['auto', 'always', 'never'], - help='show colored diff (default: auto)') - parser.add_argument( - '-e', - '--exclude', - metavar='PATTERN', - action='append', - default=[], - help='exclude paths matching the given glob-like pattern(s)' - ' from recursive search') - - args = parser.parse_args() - - # use default signal handling, like diff return SIGINT value on ^C - # https://bugs.python.org/issue14229#msg156446 - signal.signal(signal.SIGINT, signal.SIG_DFL) - try: - signal.SIGPIPE - except AttributeError: - # compatibility, SIGPIPE does not exist on Windows - pass - else: - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - - colored_stdout = False - colored_stderr = False - if args.color == 'always': - colored_stdout = True - colored_stderr = True - elif args.color == 'auto': - colored_stdout = sys.stdout.isatty() - colored_stderr = sys.stderr.isatty() - - version_invocation = [args.clang_format_executable, str("--version")] - try: - subprocess.check_call(version_invocation, stdout=DEVNULL) - except subprocess.CalledProcessError as e: - print_trouble(parser.prog, str(e), use_colors=colored_stderr) - return ExitStatus.TROUBLE - except OSError as e: - print_trouble( - parser.prog, - "Command '{}' failed to start: {}".format( - subprocess.list2cmdline(version_invocation), e - ), - use_colors=colored_stderr, - ) - return ExitStatus.TROUBLE - - retcode = ExitStatus.SUCCESS - files = list_files( - args.files, - recursive=args.recursive, - exclude=args.exclude, - extensions=args.extensions.split(',')) - - if not files: - return - - njobs = args.j - if njobs == 0: - njobs = multiprocessing.cpu_count() + 1 - njobs = min(len(files), njobs) - - if njobs == 1: - # execute directly instead of in a pool, - # less overhead, simpler stacktraces - it = (run_clang_format_diff_wrapper(args, file) for file in files) - pool = None - else: - pool = multiprocessing.Pool(njobs) - it = pool.imap_unordered( - partial(run_clang_format_diff_wrapper, args), files) - while True: - try: - outs, errs = next(it) - except StopIteration: - break - except DiffError as e: - print_trouble(parser.prog, str(e), use_colors=colored_stderr) - retcode = ExitStatus.TROUBLE - sys.stderr.writelines(e.errs) - except UnexpectedError as e: - print_trouble(parser.prog, str(e), use_colors=colored_stderr) - sys.stderr.write(e.formatted_traceback) - retcode = ExitStatus.TROUBLE - # stop at the first unexpected error, - # something could be very wrong, - # don't process all files unnecessarily - if pool: - pool.terminate() - break - else: - sys.stderr.writelines(errs) - if outs == []: - continue - if not args.quiet: - print_diff(outs, use_color=colored_stdout) - if retcode == ExitStatus.SUCCESS: - retcode = ExitStatus.DIFF - return retcode - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/.travis_scripts/run-clang-format.sh b/.travis_scripts/run-clang-format.sh deleted file mode 100755 index 9197284..0000000 --- a/.travis_scripts/run-clang-format.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -python $DIR/run-clang-format.py -r $DIR/../src/**/ $DIR/../include/**/ \ No newline at end of file diff --git a/.travis_scripts/travis.before_install.linux.sh b/.travis_scripts/travis.before_install.linux.sh deleted file mode 100644 index 9b556de..0000000 --- a/.travis_scripts/travis.before_install.linux.sh +++ /dev/null @@ -1,8 +0,0 @@ -set -vex - -# Preinstalled versions of python are dependent on which Ubuntu distribution -# you are running. The below version needs to be updated whenever we roll -# the Ubuntu version used in Travis. -# https://docs.travis-ci.com/user/languages/python/ - -pyenv global 3.7.1 diff --git a/.travis_scripts/travis.before_install.osx.sh b/.travis_scripts/travis.before_install.osx.sh deleted file mode 100644 index e69de29..0000000 diff --git a/.travis_scripts/travis.install.linux.sh b/.travis_scripts/travis.install.linux.sh deleted file mode 100644 index 6495fef..0000000 --- a/.travis_scripts/travis.install.linux.sh +++ /dev/null @@ -1,5 +0,0 @@ -set -vex - -pip3 install --user meson ninja -which meson -which ninja diff --git a/.travis_scripts/travis.install.osx.sh b/.travis_scripts/travis.install.osx.sh deleted file mode 100644 index 5d83c0c..0000000 --- a/.travis_scripts/travis.install.osx.sh +++ /dev/null @@ -1 +0,0 @@ -# NOTHING TO DO HERE diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..6d7ac3d --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,37 @@ +licenses(["unencumbered"]) # Public Domain or MIT + +exports_files(["LICENSE"]) + +cc_library( + name = "jsoncpp", + srcs = [ + "src/lib_json/json_reader.cpp", + "src/lib_json/json_tool.h", + "src/lib_json/json_value.cpp", + "src/lib_json/json_writer.cpp", + ], + hdrs = [ + "include/json/allocator.h", + "include/json/assertions.h", + "include/json/config.h", + "include/json/json_features.h", + "include/json/forwards.h", + "include/json/json.h", + "include/json/reader.h", + "include/json/value.h", + "include/json/version.h", + "include/json/writer.h", + ], + copts = [ + "-DJSON_USE_EXCEPTION=0", + "-DJSON_HAS_INT64", + ], + includes = ["include"], + visibility = ["//visibility:public"], + deps = [":private"], +) + +cc_library( + name = "private", + textual_hdrs = ["src/lib_json/json_valueiterator.inl"], +) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1db5e3..f11425c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ # policies that provide successful builds. By setting JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION # to a value greater than the oldest policies, all policies between # JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION and CMAKE_VERSION (used for this build) -# are set to their NEW behaivor, thereby suppressing policy warnings related to policies +# are set to their NEW behavior, thereby suppressing policy warnings related to policies # between the JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION and CMAKE_VERSION. # # CMake versions greater than the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION policies will @@ -22,6 +22,9 @@ else() set(JSONCPP_CMAKE_POLICY_VERSION "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}") endif() cmake_policy(VERSION ${JSONCPP_CMAKE_POLICY_VERSION}) +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() # # Now enumerate specific policies newer than JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION # that may need to be individually set to NEW/OLD @@ -51,16 +54,6 @@ endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -# --------------------------------------------------------------------------- -# use ccache if found, has to be done before project() -# --------------------------------------------------------------------------- -find_program(CCACHE_EXECUTABLE "ccache" HINTS /usr/local/bin /opt/local/bin) -if(CCACHE_EXECUTABLE) - message(STATUS "use ccache") - set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}" CACHE PATH "ccache" FORCE) - set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}" CACHE PATH "ccache" FORCE) -endif() - project(jsoncpp # Note: version must be updated in three places when doing a release. This # annoying process ensures that amalgamate, CMake, and meson all report the @@ -69,11 +62,11 @@ project(jsoncpp # 2. ./include/json/version.h # 3. ./CMakeLists.txt # IMPORTANT: also update the PROJECT_SOVERSION!! - VERSION 1.9.4 # [.[.[.]]] + VERSION 1.9.6 # [.[.[.]]] LANGUAGES CXX) message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -set(PROJECT_SOVERSION 24) +set(PROJECT_SOVERSION 26) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInSourceBuilds.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/include/PreventInBuildInstalls.cmake) @@ -85,6 +78,7 @@ option(JSONCPP_WITH_STRICT_ISO "Issue all the warnings demanded by strict ISO C option(JSONCPP_WITH_PKGCONFIG_SUPPORT "Generate and install .pc files" ON) option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" ON) option(JSONCPP_WITH_EXAMPLE "Compile JsonCpp example" OFF) +option(JSONCPP_STATIC_WINDOWS_RUNTIME "Use static (MT/MTd) Windows runtime" OFF) option(BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." ON) option(BUILD_STATIC_LIBS "Build jsoncpp_lib as a static library." ON) option(BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." ON) @@ -92,12 +86,16 @@ option(BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." ON) # Adhere to GNU filesystem layout conventions include(GNUInstallDirs) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Archive output dir.") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Library output dir.") -set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "PDB (MSVC debug symbol)output dir.") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "Executable/dll output dir.") +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Archive output dir.") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Library output dir.") + set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "PDB (MSVC debug symbol)output dir.") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" CACHE PATH "Executable/dll output dir.") +endif() -set(JSONCPP_USE_SECURE_MEMORY "0" CACHE STRING "-D...=1 to use memory-wiping allocator for STL") +if(JSONCPP_USE_SECURE_MEMORY) + add_definitions("-DJSONCPP_USE_SECURE_MEMORY=1") +endif() configure_file("${PROJECT_SOURCE_DIR}/version.in" "${PROJECT_BINARY_DIR}/version" @@ -123,11 +121,18 @@ if(MSVC) # Only enabled in debug because some old versions of VS STL generate # unreachable code warning when compiled in release configuration. add_compile_options($<$:/W4>) + if (JSONCPP_STATIC_WINDOWS_RUNTIME) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # using regular Clang or AppleClang - add_compile_options(-Wall -Wconversion -Wshadow -Werror=conversion -Werror=sign-compare) + add_compile_options(-Wall -Wconversion -Wshadow) + + if(JSONCPP_WITH_WARNING_AS_ERROR) + add_compile_options(-Werror=conversion -Werror=sign-compare) + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # using GCC add_compile_options(-Wall -Wconversion -Wshadow -Wextra) @@ -141,9 +146,11 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel") # using Intel compiler - add_compile_options(-Wall -Wconversion -Wshadow -Wextra -Werror=conversion) + add_compile_options(-Wall -Wconversion -Wshadow -Wextra) - if(JSONCPP_WITH_STRICT_ISO AND NOT JSONCPP_WITH_WARNING_AS_ERROR) + if(JSONCPP_WITH_WARNING_AS_ERROR) + add_compile_options(-Werror=conversion) + elseif(JSONCPP_WITH_STRICT_ISO) add_compile_options(-Wpedantic) endif() endif() @@ -170,11 +177,16 @@ if(JSONCPP_WITH_CMAKE_PACKAGE) include(CMakePackageConfigHelpers) install(EXPORT jsoncpp DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp - FILE jsoncppConfig.cmake) + FILE jsoncpp-targets.cmake) + configure_package_config_file(jsoncppConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp) + write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp-namespaced-targets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d992be..5f5c032 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ See `doxybuild.py --help` for options. To add a test, you need to create two files in test/data: * a `TESTNAME.json` file, that contains the input document in JSON format. -* a `TESTNAME.expected` file, that contains a flatened representation of the +* a `TESTNAME.expected` file, that contains a flattened representation of the input document. The `TESTNAME.expected` file format is as follows: diff --git a/LICENSE b/LICENSE index 89280a6..c41a1d1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,25 @@ -The JsonCpp library's source code, including accompanying documentation, +The JsonCpp library's source code, including accompanying documentation, tests and demonstration applications, are licensed under the following conditions... -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, this software is released into the Public Domain. In jurisdictions which do not recognize Public Domain property (e.g. Germany as of 2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors, and is released under the terms of the MIT License (see below). -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual Public Domain/MIT License conditions described here, as they choose. The MIT License is about as close to Public Domain as a license can get, and is described in clear, concise terms at: http://en.wikipedia.org/wiki/MIT_License - + The full text of the MIT License follows: ======================================================================== diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..67af883 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives us time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +Please submit the report by filling out +[this form](https://github.com/open-source-parsers/jsoncpp/security/advisories/new). + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by volunteers on a reasonable-effort basis. As such, +we ask that you give us 90 days to work on a fix before public exposure. diff --git a/amalgamate.py b/amalgamate.py index 4a328ab..1d1e488 100755 --- a/amalgamate.py +++ b/amalgamate.py @@ -63,7 +63,7 @@ def amalgamate_source(source_top_dir=None, """ print("Amalgamating header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgamated header (http://jsoncpp.sourceforge.net/).") + header.add_text("/// Json-cpp amalgamated header (https://github.com/open-source-parsers/jsoncpp/).") header.add_text('/// It is intended to be used with #include "%s"' % header_include_path) header.add_file("LICENSE", wrap_in_comment=True) header.add_text("#ifndef JSON_AMALGAMATED_H_INCLUDED") @@ -90,7 +90,7 @@ def amalgamate_source(source_top_dir=None, forward_header_include_path = base + "-forwards" + ext print("Amalgamating forward header...") header = AmalgamationFile(source_top_dir) - header.add_text("/// Json-cpp amalgamated forward header (http://jsoncpp.sourceforge.net/).") + header.add_text("/// Json-cpp amalgamated forward header (https://github.com/open-source-parsers/jsoncpp/).") header.add_text('/// It is intended to be used with #include "%s"' % forward_header_include_path) header.add_text("/// This header provides forward declaration for all JsonCpp types.") header.add_file("LICENSE", wrap_in_comment=True) @@ -112,7 +112,7 @@ def amalgamate_source(source_top_dir=None, print("Amalgamating source...") source = AmalgamationFile(source_top_dir) - source.add_text("/// Json-cpp amalgamated source (http://jsoncpp.sourceforge.net/).") + source.add_text("/// Json-cpp amalgamated source (https://github.com/open-source-parsers/jsoncpp/).") source.add_text('/// It is intended to be used with #include "%s"' % header_include_path) source.add_file("LICENSE", wrap_in_comment=True) source.add_text("") diff --git a/dev.makefile b/dev.makefile index 1a4be6a..545ff27 100644 --- a/dev.makefile +++ b/dev.makefile @@ -1,9 +1,11 @@ # This is only for jsoncpp developers/contributors. # We use this to sign releases, generate documentation, etc. -VER?=$(shell cat version.txt) +VER?=$(shell cat version) default: @echo "VER=${VER}" +update-version: + perl get_version.pl meson.build >| version sign: jsoncpp-${VER}.tar.gz gpg --armor --detach-sign $< gpg --verify $<.asc diff --git a/devtools/antglob.py b/devtools/antglob.py index 9843765..bd2d7ae 100644 --- a/devtools/antglob.py +++ b/devtools/antglob.py @@ -146,7 +146,7 @@ def glob(dir_path, entry_type = is_file and FILE_LINK or DIR_LINK else: entry_type = is_file and FILE or DIR -## print '=> type: %d' % entry_type, +## print '=> type: %d' % entry_type, if (entry_type & entry_type_filter) != 0: ## print ' => KEEP' yield os.path.join(dir_path, entry) diff --git a/devtools/fixeol.py b/devtools/fixeol.py index 45252a0..11e1ce2 100644 --- a/devtools/fixeol.py +++ b/devtools/fixeol.py @@ -32,8 +32,8 @@ def fix_source_eol(path, is_dry_run = True, verbose = True, eol = '\n'): if verbose: print(is_dry_run and ' NEED FIX' or ' FIXED') return True -## -## +## +## ## ##def _do_fix(is_dry_run = True): ## from waftools import antglob diff --git a/devtools/licenseupdater.py b/devtools/licenseupdater.py index 36bdb5c..d9b662e 100644 --- a/devtools/licenseupdater.py +++ b/devtools/licenseupdater.py @@ -20,7 +20,7 @@ def update_license(path, dry_run, show_diff): dry_run: if True, just print the path of the file that would be updated, but don't change it. show_diff: if True, print the path of the file that would be modified, - as well as the change made to the file. + as well as the change made to the file. """ with open(path, 'rt') as fin: original_text = fin.read().replace('\r\n','\n') @@ -51,7 +51,7 @@ def update_license_in_source_directories(source_dirs, dry_run, show_diff): dry_run: if True, just print the path of the file that would be updated, but don't change it. show_diff: if True, print the path of the file that would be modified, - as well as the change made to the file. + as well as the change made to the file. """ from devtools import antglob prune_dirs = antglob.prune_dirs + 'scons-local* ./build* ./libs ./dist' diff --git a/doxybuild.py b/doxybuild.py index 862c1f4..254ab71 100644 --- a/doxybuild.py +++ b/doxybuild.py @@ -46,7 +46,7 @@ def do_subst_in_file(targetfile, sourcefile, dict): with open(sourcefile, 'r') as f: contents = f.read() for (k,v) in list(dict.items()): - v = v.replace('\\','\\\\') + v = v.replace('\\','\\\\') contents = re.sub(k, v, contents) with open(targetfile, 'w') as f: f.write(contents) @@ -158,7 +158,7 @@ def main(): Generates doxygen documentation in build/doxygen. Optionally makes a tarball of the documentation to dist/. - Must be started in the project top directory. + Must be started in the project top directory. """ from optparse import OptionParser parser = OptionParser(usage=usage) diff --git a/example/README.md b/example/README.md index b1ae4c8..92b925c 100644 --- a/example/README.md +++ b/example/README.md @@ -1,8 +1,8 @@ -***NOTE*** +***NOTE*** If you get linker errors about undefined references to symbols that involve types in the `std::__cxx11` namespace or the tag `[abi:cxx11]` then it probably indicates that you are trying to link together object files that were compiled with different -values for the _GLIBCXX_USE_CXX11_ABI marco. This commonly happens when linking to a third-party library that was compiled with +values for the _GLIBCXX_USE_CXX11_ABI marco. This commonly happens when linking to a third-party library that was compiled with an older version of GCC. If the third-party library cannot be rebuilt with the new ABI, then you need to recompile your code with the old ABI,just like: **g++ stringWrite.cpp -ljsoncpp -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -o stringWrite** diff --git a/example/readFromString/readFromString.cpp b/example/readFromString/readFromString.cpp index c27bbd5..878f9eb 100644 --- a/example/readFromString/readFromString.cpp +++ b/example/readFromString/readFromString.cpp @@ -1,5 +1,6 @@ #include "json/json.h" #include +#include /** * \brief Parse a raw string into Value object using the CharReaderBuilder * class, or the legacy Reader class. @@ -24,7 +25,7 @@ int main() { const std::unique_ptr reader(builder.newCharReader()); if (!reader->parse(rawJson.c_str(), rawJson.c_str() + rawJsonLength, &root, &err)) { - std::cout << "error" << std::endl; + std::cout << "error: " << err << std::endl; return EXIT_FAILURE; } } diff --git a/example/streamWrite/streamWrite.cpp b/example/streamWrite/streamWrite.cpp index 6f7f797..a72f5a5 100644 --- a/example/streamWrite/streamWrite.cpp +++ b/example/streamWrite/streamWrite.cpp @@ -1,5 +1,6 @@ #include "json/json.h" #include +#include /** \brief Write the Value object to a stream. * Example Usage: * $g++ streamWrite.cpp -ljsoncpp -std=c++11 -o streamWrite diff --git a/get_version.pl b/get_version.pl new file mode 100644 index 0000000..19b6a54 --- /dev/null +++ b/get_version.pl @@ -0,0 +1,5 @@ +while (<>) { + if (/version : '(.+)',/) { + print "$1"; + } +} diff --git a/include/PreventInSourceBuilds.cmake b/include/PreventInSourceBuilds.cmake index 7ddda54..be5d0dd 100644 --- a/include/PreventInSourceBuilds.cmake +++ b/include/PreventInSourceBuilds.cmake @@ -2,8 +2,8 @@ # This function will prevent in-source builds function(AssureOutOfSourceBuilds) # make sure the user doesn't play dirty with symlinks - get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) - get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + get_filename_component(srcdir "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_CURRENT_BINARY_DIR}" REALPATH) # disallow in-source builds if("${srcdir}" STREQUAL "${bindir}") diff --git a/include/json/allocator.h b/include/json/allocator.h index 95ef8a5..f4fcc1c 100644 --- a/include/json/allocator.h +++ b/include/json/allocator.h @@ -9,7 +9,8 @@ #include #include -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { template class SecureAllocator { @@ -68,7 +69,9 @@ public: // Boilerplate SecureAllocator() {} template SecureAllocator(const SecureAllocator&) {} - template struct rebind { using other = SecureAllocator; }; + template struct rebind { + using other = SecureAllocator; + }; }; template diff --git a/include/json/json_features.h b/include/json/json_features.h index 7c7e9f5..e4a61d6 100644 --- a/include/json/json_features.h +++ b/include/json/json_features.h @@ -10,7 +10,8 @@ #include "forwards.h" #endif // if !defined(JSON_IS_AMALGAMATION) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { diff --git a/include/json/reader.h b/include/json/reader.h index 9175466..d745378 100644 --- a/include/json/reader.h +++ b/include/json/reader.h @@ -23,7 +23,8 @@ #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -33,8 +34,7 @@ namespace Json { * \deprecated Use CharReader and CharReaderBuilder. */ -class JSONCPP_DEPRECATED( - "Use CharReader and CharReaderBuilder instead.") JSON_API Reader { +class JSON_API Reader { public: using Char = char; using Location = const Char*; @@ -51,13 +51,13 @@ public: }; /** \brief Constructs a Reader allowing all features for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ - JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(); /** \brief Constructs a Reader allowing the specified feature set for parsing. + * \deprecated Use CharReader and CharReaderBuilder. */ - JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead") Reader(const Features& features); /** \brief Read a Value from a JSON @@ -190,6 +190,7 @@ private: using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); bool match(const Char* pattern, int patternLength); bool readComment(); @@ -221,7 +222,6 @@ private: int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static bool containsNewLine(Location begin, Location end); static String normalizeEOL(Location begin, Location end); @@ -244,6 +244,12 @@ private: */ class JSON_API CharReader { public: + struct JSON_API StructuredError { + ptrdiff_t offset_start; + ptrdiff_t offset_limit; + String message; + }; + virtual ~CharReader() = default; /** \brief Read a Value from a JSON * document. The document must be a UTF-8 encoded string containing the @@ -262,7 +268,12 @@ public: * error occurred. */ virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) = 0; + String* errs); + + /** \brief Returns a vector of structured errors encountered while parsing. + * Each parse call resets the stored list of errors. + */ + std::vector getStructuredErrors() const; class JSON_API Factory { public: @@ -272,7 +283,21 @@ public: */ virtual CharReader* newCharReader() const = 0; }; // Factory -}; // CharReader + +protected: + class Impl { + public: + virtual ~Impl() = default; + virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) = 0; + virtual std::vector getStructuredErrors() const = 0; + }; + + explicit CharReader(std::unique_ptr impl) : _impl(std::move(impl)) {} + +private: + std::unique_ptr _impl; +}; // CharReader /** \brief Build a CharReader implementation. * @@ -324,6 +349,9 @@ public: * - `"allowSpecialFloats": false or true` * - If true, special float values (NaNs and infinities) are allowed and * their values are lossfree restorable. + * - `"skipBom": false or true` + * - If true, if the input starts with the Unicode byte order mark (BOM), + * it is skipped. * * You can examine 'settings_` yourself to see the defaults. You can also * write and read them just like any JSON Value. @@ -357,6 +385,12 @@ public: * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode */ static void strictMode(Json::Value* settings); + /** ECMA-404 mode. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderECMA404Mode + */ + static void ecma404Mode(Json::Value* settings); }; /** Consume entire stream and use its begin/end. diff --git a/include/json/value.h b/include/json/value.h index ec9af56..c8e1aae 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -3,8 +3,8 @@ // recognized in your jurisdiction. // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE -#ifndef JSON_H_INCLUDED -#define JSON_H_INCLUDED +#ifndef JSON_VALUE_H_INCLUDED +#define JSON_VALUE_H_INCLUDED #if !defined(JSON_IS_AMALGAMATION) #include "forwards.h" @@ -50,10 +50,11 @@ // be used by... #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #pragma warning(push) -#pragma warning(disable : 4251) +#pragma warning(disable : 4251 4275) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() /** \brief JSON (JavaScript Object Notation). */ @@ -436,7 +437,7 @@ public: /// \post type() is arrayValue void resize(ArrayIndex newSize); - //@{ + ///@{ /// Access an array element (zero based index). If the array contains less /// than index element, then null value are inserted in the array so that /// its size is index+1. @@ -444,15 +445,15 @@ public: /// this from the operator[] which takes a string.) Value& operator[](ArrayIndex index); Value& operator[](int index); - //@} + ///@} - //@{ + ///@{ /// Access an array element (zero based index). /// (You may need to say 'value[0u]' to get your compiler to distinguish /// this from the operator[] which takes a string.) const Value& operator[](ArrayIndex index) const; const Value& operator[](int index) const; - //@} + ///@} /// If the array contains at least index+1 elements, returns the element /// value, otherwise returns defaultValue. @@ -512,6 +513,9 @@ public: /// and operator[]const /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + Value const* find(const String& key) const; /// Most general and efficient version of object-mutators. /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. @@ -584,6 +588,26 @@ public: iterator begin(); iterator end(); + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + const Value& front() const; + + /// \brief Returns a reference to the first element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& front(); + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that value holds an array or json object, with at least one + /// element. + const Value& back() const; + + /// \brief Returns a reference to the last element in the `Value`. + /// Requires that this value holds an array or json object, with at least one + /// element. + Value& back(); + // Accessors for the [start, limit) range of bytes within the JSON text from // which this value was parsed, if any. void setOffsetStart(ptrdiff_t start); @@ -918,12 +942,20 @@ public: * because the returned references/pointers can be used * to change state of the base class. */ - reference operator*() { return deref(); } - pointer operator->() { return &deref(); } + reference operator*() const { return const_cast(deref()); } + pointer operator->() const { return const_cast(&deref()); } }; inline void swap(Value& a, Value& b) { a.swap(b); } +inline const Value& Value::front() const { return *begin(); } + +inline Value& Value::front() { return *begin(); } + +inline const Value& Value::back() const { return *(--end()); } + +inline Value& Value::back() { return *(--end()); } + } // namespace Json #pragma pack(pop) diff --git a/include/json/version.h b/include/json/version.h index 87cf7e2..38faedf 100644 --- a/include/json/version.h +++ b/include/json/version.h @@ -9,19 +9,18 @@ // 3. /CMakeLists.txt // IMPORTANT: also update the SOVERSION!! -#define JSONCPP_VERSION_STRING "1.9.4" +#define JSONCPP_VERSION_STRING "1.9.6" #define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MINOR 9 -#define JSONCPP_VERSION_PATCH 4 +#define JSONCPP_VERSION_PATCH 6 #define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_HEXA \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ (JSONCPP_VERSION_PATCH << 8)) -#ifdef JSONCPP_USING_SECURE_MEMORY -#undef JSONCPP_USING_SECURE_MEMORY -#endif +#if !defined(JSONCPP_USE_SECURE_MEMORY) #define JSONCPP_USING_SECURE_MEMORY 0 +#endif // If non-zero, the library zeroes any memory that it has allocated before // it frees its memory. diff --git a/include/json/writer.h b/include/json/writer.h index fb0852a..7c56a21 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -20,7 +20,8 @@ #pragma warning(disable : 4251) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) -#pragma pack(push, 8) +#pragma pack(push) +#pragma pack() namespace Json { @@ -63,7 +64,7 @@ public: */ virtual StreamWriter* newStreamWriter() const = 0; }; // Factory -}; // StreamWriter +}; // StreamWriter /** \brief Write into stringstream, then return string, for convenience. * A StreamWriter will be created from the factory, used, and then deleted. @@ -110,6 +111,8 @@ public: * - Number of precision digits for formatting of real values. * - "precisionType": "significant"(default) or "decimal" * - Type of precision for formatting of real values. + * - "emitUTF8": false or true + * - If true, outputs raw UTF8 strings instead of escaping them. * You can examine 'settings_` yourself * to see the defaults. You can also write and read them just like any @@ -145,7 +148,7 @@ public: /** \brief Abstract class for writers. * \deprecated Use StreamWriter. (And really, this is an implementation detail.) */ -class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { +class JSON_API Writer { public: virtual ~Writer(); @@ -165,8 +168,7 @@ public: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter - : public Writer { +class JSON_API FastWriter : public Writer { public: FastWriter(); ~FastWriter() override = default; @@ -215,7 +217,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their *#CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -225,8 +227,7 @@ private: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API - StyledWriter : public Writer { +class JSON_API StyledWriter : public Writer { public: StyledWriter(); ~StyledWriter() override = default; @@ -284,7 +285,7 @@ private: * - otherwise, it the values do not fit on one line, or the array contains * object or non empty array, then print one value per line. * - * If the Value have comments then they are outputed according to their + * If the Value have comments then they are outputted according to their #CommentPlacement. * * \sa Reader, Value, Value::setComment() @@ -294,8 +295,7 @@ private: #pragma warning(push) #pragma warning(disable : 4996) // Deriving from deprecated class #endif -class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API - StyledStreamWriter { +class JSON_API StyledStreamWriter { public: /** * \param indentation Each level will be indented by this amount extra. @@ -351,6 +351,7 @@ String JSON_API valueToString( PrecisionType precisionType = PrecisionType::significantDigits); String JSON_API valueToString(bool value); String JSON_API valueToQuotedString(const char* value); +String JSON_API valueToQuotedString(const char* value, size_t length); /// \brief Output using the StyledStreamWriter. /// \see Json::operator>>() diff --git a/jsoncpp-namespaced-targets.cmake b/jsoncpp-namespaced-targets.cmake new file mode 100644 index 0000000..70a79ee --- /dev/null +++ b/jsoncpp-namespaced-targets.cmake @@ -0,0 +1,9 @@ +if (NOT TARGET JsonCpp::JsonCpp) + if (TARGET jsoncpp_static) + add_library(JsonCpp::JsonCpp INTERFACE IMPORTED) + set_target_properties(JsonCpp::JsonCpp PROPERTIES INTERFACE_LINK_LIBRARIES "jsoncpp_static") + elseif (TARGET jsoncpp_lib) + add_library(JsonCpp::JsonCpp INTERFACE IMPORTED) + set_target_properties(JsonCpp::JsonCpp PROPERTIES INTERFACE_LINK_LIBRARIES "jsoncpp_lib") + endif () +endif () diff --git a/jsoncppConfig.cmake.in b/jsoncppConfig.cmake.in new file mode 100644 index 0000000..fdd9fea --- /dev/null +++ b/jsoncppConfig.cmake.in @@ -0,0 +1,11 @@ +cmake_policy(PUSH) +cmake_policy(VERSION 3.0...3.26) + +@PACKAGE_INIT@ + +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-targets.cmake" ) +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-namespaced-targets.cmake" ) + +check_required_components(JsonCpp) + +cmake_policy(POP) diff --git a/jsoncppConfig.cmake.meson.in b/jsoncppConfig.cmake.meson.in new file mode 100644 index 0000000..0f4866d --- /dev/null +++ b/jsoncppConfig.cmake.meson.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +@MESON_SHARED_TARGET@ +@MESON_STATIC_TARGET@ + +include ( "${CMAKE_CURRENT_LIST_DIR}/jsoncpp-namespaced-targets.cmake" ) + +check_required_components(JsonCpp) diff --git a/meson.build b/meson.build index 08e0f29..561b41c 100644 --- a/meson.build +++ b/meson.build @@ -9,13 +9,13 @@ project( # 2. /include/json/version.h # 3. /CMakeLists.txt # IMPORTANT: also update the SOVERSION!! - version : '1.9.4', + version : '1.9.6', default_options : [ 'buildtype=release', 'cpp_std=c++11', 'warning_level=1'], license : 'Public Domain', - meson_version : '>= 0.49.0') + meson_version : '>= 0.54.0') jsoncpp_headers = files([ @@ -50,7 +50,7 @@ jsoncpp_lib = library( 'src/lib_json/json_value.cpp', 'src/lib_json/json_writer.cpp', ]), - soversion : 24, + soversion : 26, install : true, include_directories : jsoncpp_include_directories, cpp_args: dll_export_flag) @@ -62,6 +62,43 @@ import('pkgconfig').generate( filebase : 'jsoncpp', description : 'A C++ library for interacting with JSON') +cmakeconf = configuration_data() +cmakeconf.set('MESON_LIB_DIR', get_option('libdir')) +cmakeconf.set('MESON_INCLUDE_DIR', get_option('includedir')) + +fs = import('fs') +if get_option('default_library') == 'shared' + shared_name = fs.name(jsoncpp_lib.full_path()) +endif +if get_option('default_library') == 'static' + static_name = fs.name(jsoncpp_lib.full_path()) +endif +if get_option('default_library') == 'both' + shared_name = fs.name(jsoncpp_lib.get_shared_lib().full_path()) + static_name = fs.name(jsoncpp_lib.get_static_lib().full_path()) +endif + +if get_option('default_library') == 'shared' or get_option('default_library') == 'both' + cmakeconf.set('MESON_SHARED_TARGET', ''' +add_library(jsoncpp_lib IMPORTED SHARED) +set_target_properties(jsoncpp_lib PROPERTIES + IMPORTED_LOCATION "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('libdir'), shared_name) + '''" + INTERFACE_INCLUDE_DIRECTORIES "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('includedir')) + '")') +endif +if get_option('default_library') == 'static' or get_option('default_library') == 'both' + cmakeconf.set('MESON_STATIC_TARGET', ''' +add_library(jsoncpp_static IMPORTED STATIC) +set_target_properties(jsoncpp_static PROPERTIES + IMPORTED_LOCATION "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('libdir'), static_name) + '''" + INTERFACE_INCLUDE_DIRECTORIES "''' + join_paths('${PACKAGE_PREFIX_DIR}', get_option('includedir')) + '")') +endif + +import('cmake').configure_package_config_file( + name: 'jsoncpp', + input: 'jsoncppConfig.cmake.meson.in', + configuration: cmakeconf) +install_data('jsoncpp-namespaced-targets.cmake', install_dir : join_paths(get_option('libdir'), 'cmake', jsoncpp_lib.name())) + # for libraries bundling jsoncpp jsoncpp_dep = declare_dependency( include_directories : jsoncpp_include_directories, @@ -73,7 +110,7 @@ if meson.is_subproject() or not get_option('tests') subdir_done() endif -python = import('python').find_installation() +python = find_program('python3') jsoncpp_test = executable( 'jsoncpp_test', files([ diff --git a/pkg-config/jsoncpp.pc.in b/pkg-config/jsoncpp.pc.in index 632a377..2a22210 100644 --- a/pkg-config/jsoncpp.pc.in +++ b/pkg-config/jsoncpp.pc.in @@ -5,7 +5,7 @@ includedir=@includedir_for_pc_file@ Name: jsoncpp Description: A C++ library for interacting with JSON -Version: @JSONCPP_VERSION@ +Version: @PROJECT_VERSION@ URL: https://github.com/open-source-parsers/jsoncpp Libs: -L${libdir} -ljsoncpp Cflags: -I${includedir} diff --git a/src/jsontestrunner/main.cpp b/src/jsontestrunner/main.cpp index 3452c59..ab6a800 100644 --- a/src/jsontestrunner/main.cpp +++ b/src/jsontestrunner/main.cpp @@ -240,11 +240,14 @@ static int parseCommandLine(int argc, const char* argv[], Options* opts) { return printUsage(argv); } int index = 1; - if (Json::String(argv[index]) == "--json-checker") { - opts->features = Json::Features::strictMode(); + if (Json::String(argv[index]) == "--parse-only") { opts->parseOnly = true; ++index; } + if (Json::String(argv[index]) == "--strict") { + opts->features = Json::Features::strictMode(); + ++index; + } if (Json::String(argv[index]) == "--json-config") { printConfig(); return 3; @@ -335,6 +338,7 @@ int main(int argc, const char* argv[]) { std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl; return 1; } + return 0; } #if defined(__GNUC__) diff --git a/src/lib_json/CMakeLists.txt b/src/lib_json/CMakeLists.txt index af26476..1526353 100644 --- a/src/lib_json/CMakeLists.txt +++ b/src/lib_json/CMakeLists.txt @@ -11,20 +11,10 @@ include(CheckCXXSymbolExists) check_include_file_cxx(clocale HAVE_CLOCALE) check_cxx_symbol_exists(localeconv clocale HAVE_LOCALECONV) -if(CMAKE_VERSION VERSION_LESS 3.0.0) - # The "LANGUAGE CXX" parameter is not supported in CMake versions below 3, - # so the C compiler and header has to be used. - check_include_file(locale.h HAVE_LOCALE_H) - set(CMAKE_EXTRA_INCLUDE_FILES locale.h) - check_type_size("struct lconv" LCONV_SIZE) - unset(CMAKE_EXTRA_INCLUDE_FILES) - check_struct_has_member("struct lconv" decimal_point locale.h HAVE_DECIMAL_POINT) -else() - set(CMAKE_EXTRA_INCLUDE_FILES clocale) - check_type_size(lconv LCONV_SIZE LANGUAGE CXX) - unset(CMAKE_EXTRA_INCLUDE_FILES) - check_struct_has_member(lconv decimal_point clocale HAVE_DECIMAL_POINT LANGUAGE CXX) -endif() +set(CMAKE_EXTRA_INCLUDE_FILES clocale) +check_type_size(lconv LCONV_SIZE LANGUAGE CXX) +unset(CMAKE_EXTRA_INCLUDE_FILES) +check_struct_has_member(lconv decimal_point clocale HAVE_DECIMAL_POINT LANGUAGE CXX) if(NOT (HAVE_CLOCALE AND HAVE_LCONV_SIZE AND HAVE_DECIMAL_POINT AND HAVE_LOCALECONV)) message(WARNING "Locale functionality is not supported") @@ -129,7 +119,7 @@ if(BUILD_SHARED_LIBS) OUTPUT_NAME jsoncpp VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_SOVERSION} - POSITION_INDEPENDENT_CODE ON + POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} ) # Set library's runtime search path on OSX @@ -139,13 +129,10 @@ if(BUILD_SHARED_LIBS) target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(NOT CMAKE_VERSION VERSION_LESS 2.8.11) - target_include_directories(${SHARED_LIB} PUBLIC - $ - $ - $ - ) - endif() + target_include_directories(${SHARED_LIB} PUBLIC + $ + $ + ) list(APPEND CMAKE_TARGETS ${SHARED_LIB}) endif() @@ -154,9 +141,13 @@ if(BUILD_STATIC_LIBS) set(STATIC_LIB ${PROJECT_NAME}_static) add_library(${STATIC_LIB} STATIC ${PUBLIC_HEADERS} ${JSONCPP_SOURCES}) - # avoid name clashes on windows as the shared import lib is alse named jsoncpp.lib + # avoid name clashes on windows as the shared import lib is also named jsoncpp.lib if(NOT DEFINED STATIC_SUFFIX AND BUILD_SHARED_LIBS) - set(STATIC_SUFFIX "_static") + if (WIN32) + set(STATIC_SUFFIX "_static") + else() + set(STATIC_SUFFIX "") + endif() endif() set_target_properties(${STATIC_LIB} PROPERTIES @@ -171,13 +162,10 @@ if(BUILD_STATIC_LIBS) target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(NOT CMAKE_VERSION VERSION_LESS 2.8.11) - target_include_directories(${STATIC_LIB} PUBLIC - $ - $ - $ - ) - endif() + target_include_directories(${STATIC_LIB} PUBLIC + $ + $ + ) list(APPEND CMAKE_TARGETS ${STATIC_LIB}) endif() @@ -190,7 +178,7 @@ if(BUILD_OBJECT_LIBS) OUTPUT_NAME jsoncpp VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_SOVERSION} - POSITION_INDEPENDENT_CODE ON + POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} ) # Set library's runtime search path on OSX @@ -200,13 +188,10 @@ if(BUILD_OBJECT_LIBS) target_compile_features(${OBJECT_LIB} PUBLIC ${REQUIRED_FEATURES}) - if(NOT CMAKE_VERSION VERSION_LESS 2.8.11) - target_include_directories(${OBJECT_LIB} PUBLIC - $ - $ - $ - ) - endif() + target_include_directories(${OBJECT_LIB} PUBLIC + $ + $ + ) list(APPEND CMAKE_TARGETS ${OBJECT_LIB}) endif() diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp index a34017d..10c97ae 100644 --- a/src/lib_json/json_reader.cpp +++ b/src/lib_json/json_reader.cpp @@ -12,6 +12,7 @@ #endif // if !defined(JSON_IS_AMALGAMATION) #include #include +#include #include #include #include @@ -104,8 +105,7 @@ bool Reader::parse(std::istream& is, Value& root, bool collectComments) { // Since String is reference-counted, this at least does not // create an extra copy. - String doc; - std::getline(is, doc, static_cast EOF); + String doc(std::istreambuf_iterator(is), {}); return parse(doc.data(), doc.data() + doc.size(), root, collectComments); } @@ -129,7 +129,7 @@ bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (collectComments_ && !commentsBefore_.empty()) root.setComment(commentsBefore_, commentAfter); if (features_.strictRoot_) { @@ -157,7 +157,7 @@ bool Reader::readValue() { throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -225,14 +225,14 @@ bool Reader::readValue() { return successful; } -void Reader::skipCommentTokens(Token& token) { +bool Reader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool Reader::readToken(Token& token) { @@ -446,12 +446,7 @@ bool Reader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object return true; name.clear(); @@ -480,15 +475,11 @@ bool Reader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -518,10 +509,7 @@ bool Reader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -599,11 +587,16 @@ bool Reader::decodeDouble(Token& token) { bool Reader::decodeDouble(Token& token, Value& decoded) { double value = 0; - String buffer(token.start_, token.end_); - IStringStream is(buffer); - if (!(is >> value)) - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + IStringStream is(String(token.start_, token.end_)); + if (!(is >> value)) { + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); + } decoded = value; return true; } @@ -767,7 +760,7 @@ void Reader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -884,17 +877,12 @@ class OurReader { public: using Char = char; using Location = const Char*; - struct StructuredError { - ptrdiff_t offset_start; - ptrdiff_t offset_limit; - String message; - }; explicit OurReader(OurFeatures const& features); bool parse(const char* beginDoc, const char* endDoc, Value& root, bool collectComments = true); String getFormattedErrorMessages() const; - std::vector getStructuredErrors() const; + std::vector getStructuredErrors() const; private: OurReader(OurReader const&); // no impl @@ -937,6 +925,7 @@ private: using Errors = std::deque; bool readToken(Token& token); + bool readTokenSkippingComments(Token& token); void skipSpaces(); void skipBom(bool skipBom); bool match(const Char* pattern, int patternLength); @@ -970,7 +959,6 @@ private: int& column) const; String getLocationLineAndColumn(Location location) const; void addComment(Location begin, Location end, CommentPlacement placement); - void skipCommentTokens(Token& token); static String normalizeEOL(Location begin, Location end); static bool containsNewLine(Location begin, Location end); @@ -1024,7 +1012,7 @@ bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, bool successful = readValue(); nodes_.pop(); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { addError("Extra non-whitespace after JSON value.", token); return false; @@ -1052,7 +1040,7 @@ bool OurReader::readValue() { if (nodes_.size() > features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue()."); Token token; - skipCommentTokens(token); + readTokenSkippingComments(token); bool successful = true; if (collectComments_ && !commentsBefore_.empty()) { @@ -1139,14 +1127,14 @@ bool OurReader::readValue() { return successful; } -void OurReader::skipCommentTokens(Token& token) { +bool OurReader::readTokenSkippingComments(Token& token) { + bool success = readToken(token); if (features_.allowComments_) { - do { - readToken(token); - } while (token.type_ == tokenComment); - } else { - readToken(token); + while (success && token.type_ == tokenComment) { + success = readToken(token); + } } + return success; } bool OurReader::readToken(Token& token) { @@ -1443,12 +1431,7 @@ bool OurReader::readObject(Token& token) { Value init(objectValue); currentValue().swapPayload(init); currentValue().setOffsetStart(token.start_ - begin_); - while (readToken(tokenName)) { - bool initialTokenOk = true; - while (tokenName.type_ == tokenComment && initialTokenOk) - initialTokenOk = readToken(tokenName); - if (!initialTokenOk) - break; + while (readTokenSkippingComments(tokenName)) { if (tokenName.type_ == tokenObjectEnd && (name.empty() || features_.allowTrailingCommas_)) // empty object or trailing comma @@ -1485,15 +1468,11 @@ bool OurReader::readObject(Token& token) { return recoverFromError(tokenObjectEnd); Token comma; - if (!readToken(comma) || - (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && - comma.type_ != tokenComment)) { + if (!readTokenSkippingComments(comma) || + (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { return addErrorAndRecover("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } - bool finalizeTokenOk = true; - while (comma.type_ == tokenComment && finalizeTokenOk) - finalizeTokenOk = readToken(comma); if (comma.type_ == tokenObjectEnd) return true; } @@ -1527,10 +1506,7 @@ bool OurReader::readArray(Token& token) { Token currentToken; // Accept Comment after last item in the array. - ok = readToken(currentToken); - while (currentToken.type_ == tokenComment && ok) { - ok = readToken(currentToken); - } + ok = readTokenSkippingComments(currentToken); bool badTokenType = (currentToken.type_ != tokenArraySeparator && currentToken.type_ != tokenArrayEnd); if (!ok || badTokenType) { @@ -1608,7 +1584,7 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { const auto digit(static_cast(c - '0')); if (value >= threshold) { // We've hit or exceeded the max value divided by 10 (rounded down). If - // a) we've only just touched the limit, meaing value == threshold, + // a) we've only just touched the limit, meaning value == threshold, // b) this is the last digit, or // c) it's small enough to fit in that rounding delta, we're okay. // Otherwise treat this number as a double to avoid overflow. @@ -1645,11 +1621,15 @@ bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token, Value& decoded) { double value = 0; - const String buffer(token.start_, token.end_); - IStringStream is(buffer); + IStringStream is(String(token.start_, token.end_)); if (!(is >> value)) { - return addError( - "'" + String(token.start_, token.end_) + "' is not a number.", token); + if (value == std::numeric_limits::max()) + value = std::numeric_limits::infinity(); + else if (value == std::numeric_limits::lowest()) + value = -std::numeric_limits::infinity(); + else if (!std::isinf(value)) + return addError( + "'" + String(token.start_, token.end_) + "' is not a number.", token); } decoded = value; return true; @@ -1814,7 +1794,7 @@ void OurReader::getLocationLineAndColumn(Location location, int& line, while (current < location && current != end_) { Char c = *current++; if (c == '\r') { - if (*current == '\n') + if (current != end_ && *current == '\n') ++current; lastLineStart = current; ++line; @@ -1849,10 +1829,11 @@ String OurReader::getFormattedErrorMessages() const { return formattedMessage; } -std::vector OurReader::getStructuredErrors() const { - std::vector allErrors; +std::vector +OurReader::getStructuredErrors() const { + std::vector allErrors; for (const auto& error : errors_) { - OurReader::StructuredError structured; + CharReader::StructuredError structured; structured.offset_start = error.token_.start_ - begin_; structured.offset_limit = error.token_.end_ - begin_; structured.message = error.message_; @@ -1862,20 +1843,36 @@ std::vector OurReader::getStructuredErrors() const { } class OurCharReader : public CharReader { - bool const collectComments_; - OurReader reader_; public: OurCharReader(bool collectComments, OurFeatures const& features) - : collectComments_(collectComments), reader_(features) {} - bool parse(char const* beginDoc, char const* endDoc, Value* root, - String* errs) override { - bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); - if (errs) { - *errs = reader_.getFormattedErrorMessages(); + : CharReader( + std::unique_ptr(new OurImpl(collectComments, features))) {} + +protected: + class OurImpl : public Impl { + public: + OurImpl(bool collectComments, OurFeatures const& features) + : collectComments_(collectComments), reader_(features) {} + + bool parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) override { + bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); + if (errs) { + *errs = reader_.getFormattedErrorMessages(); + } + return ok; } - return ok; - } + + std::vector + getStructuredErrors() const override { + return reader_.getStructuredErrors(); + } + + private: + bool const collectComments_; + OurReader reader_; + }; }; CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } @@ -1964,6 +1961,32 @@ void CharReaderBuilder::setDefaults(Json::Value* settings) { (*settings)["skipBom"] = true; //! [CharReaderBuilderDefaults] } +// static +void CharReaderBuilder::ecma404Mode(Json::Value* settings) { + //! [CharReaderBuilderECMA404Mode] + (*settings)["allowComments"] = false; + (*settings)["allowTrailingCommas"] = false; + (*settings)["strictRoot"] = false; + (*settings)["allowDroppedNullPlaceholders"] = false; + (*settings)["allowNumericKeys"] = false; + (*settings)["allowSingleQuotes"] = false; + (*settings)["stackLimit"] = 1000; + (*settings)["failIfExtra"] = true; + (*settings)["rejectDupKeys"] = false; + (*settings)["allowSpecialFloats"] = false; + (*settings)["skipBom"] = false; + //! [CharReaderBuilderECMA404Mode] +} + +std::vector +CharReader::getStructuredErrors() const { + return _impl->getStructuredErrors(); +} + +bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root, + String* errs) { + return _impl->parse(beginDoc, endDoc, root, errs); +} ////////////////////////////////// // global functions @@ -1972,7 +1995,7 @@ bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, String* errs) { OStringStream ssin; ssin << sin.rdbuf(); - String doc = ssin.str(); + String doc = std::move(ssin).str(); char const* begin = doc.data(); char const* end = begin + doc.size(); // Note that we do not actually need a null-terminator. diff --git a/src/lib_json/json_tool.h b/src/lib_json/json_tool.h index 2d7b7d9..b952c19 100644 --- a/src/lib_json/json_tool.h +++ b/src/lib_json/json_tool.h @@ -116,14 +116,18 @@ template void fixNumericLocaleInput(Iter begin, Iter end) { * Return iterator that would be the new end of the range [begin,end), if we * were to delete zeros in the end of string, but not the last zero before '.'. */ -template Iter fixZerosInTheEnd(Iter begin, Iter end) { +template +Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) { for (; begin != end; --end) { if (*(end - 1) != '0') { return end; } // Don't delete the last zero before the decimal point. - if (begin != (end - 1) && *(end - 2) == '.') { - return end; + if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') { + if (precision) { + return end; + } + return end - 2; } } return end; diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index bfa9263..5bd8d9a 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -912,7 +912,8 @@ void Value::resize(ArrayIndex newSize) { if (newSize == 0) clear(); else if (newSize > oldSize) - this->operator[](newSize - 1); + for (ArrayIndex i = oldSize; i < newSize; ++i) + (*this)[i]; else { for (ArrayIndex index = newSize; index < oldSize; ++index) { value_.map_->erase(index); @@ -1091,6 +1092,9 @@ Value const* Value::find(char const* begin, char const* end) const { return nullptr; return &(*it).second; } +Value const* Value::find(const String& key) const { + return find(key.data(), key.data() + key.length()); +} Value* Value::demand(char const* begin, char const* end) { JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue, "in Json::Value::demand(begin, end): requires " @@ -1104,7 +1108,7 @@ const Value& Value::operator[](const char* key) const { return *found; } Value const& Value::operator[](const String& key) const { - Value const* found = find(key.data(), key.data() + key.length()); + Value const* found = find(key); if (!found) return nullSingleton(); return *found; @@ -1204,7 +1208,7 @@ bool Value::removeIndex(ArrayIndex index, Value* removed) { return false; } if (removed) - *removed = it->second; + *removed = std::move(it->second); ArrayIndex oldSize = size(); // shift left all items left, into the place of the "removed" for (ArrayIndex i = index; i < (oldSize - 1); ++i) { @@ -1397,13 +1401,11 @@ String Value::Comments::get(CommentPlacement slot) const { } void Value::Comments::set(CommentPlacement slot, String comment) { - if (!ptr_) { + if (slot >= CommentPlacement::numberOfCommentPlacement) + return; + if (!ptr_) ptr_ = std::unique_ptr(new Array()); - } - // check comments array boundry. - if (slot < CommentPlacement::numberOfCommentPlacement) { - (*ptr_)[slot] = std::move(comment); - } + (*ptr_)[slot] = std::move(comment); } void Value::setComment(String comment, CommentPlacement placement) { @@ -1411,9 +1413,8 @@ void Value::setComment(String comment, CommentPlacement placement) { // Always discard trailing newline, to aid indentation. comment.pop_back(); } - JSON_ASSERT(!comment.empty()); JSON_ASSERT_MESSAGE( - comment[0] == '\0' || comment[0] == '/', + comment.empty() || comment[0] == '/', "in Json::Value::setComment(): Comments must start with /"); comments_.set(placement, std::move(comment)); } diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 41d72c8..743a6a4 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -132,8 +132,9 @@ String valueToString(double value, bool useSpecialFloats, if (!isfinite(value)) { static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, {"null", "-1e+9999", "1e+9999"}}; - return reps[useSpecialFloats ? 0 : 1] - [isnan(value) ? 0 : (value < 0) ? 1 : 2]; + return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 + : (value < 0) ? 1 + : 2]; } String buffer(size_t(36), '\0'); @@ -154,16 +155,18 @@ String valueToString(double value, bool useSpecialFloats, buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); - // strip the zero padding from the right - if (precisionType == PrecisionType::decimalPlaces) { - buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end()); - } - // try to ensure we preserve the fact that this was given to us as a double on // input if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { buffer += ".0"; } + + // strip the zero padding from the right + if (precisionType == PrecisionType::decimalPlaces) { + buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision), + buffer.end()); + } + return buffer; } } // namespace @@ -270,7 +273,7 @@ static void appendHex(String& result, unsigned ch) { result.append("\\u").append(toHex16Bit(ch)); } -static String valueToQuotedStringN(const char* value, unsigned length, +static String valueToQuotedStringN(const char* value, size_t length, bool emitUTF8 = false) { if (value == nullptr) return ""; @@ -348,7 +351,11 @@ static String valueToQuotedStringN(const char* value, unsigned length, } String valueToQuotedString(const char* value) { - return valueToQuotedStringN(value, static_cast(strlen(value))); + return valueToQuotedStringN(value, strlen(value)); +} + +String valueToQuotedString(const char* value, size_t length) { + return valueToQuotedStringN(value, length); } // Class Writer @@ -397,7 +404,7 @@ void FastWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - document_ += valueToQuotedStringN(str, static_cast(end - str)); + document_ += valueToQuotedStringN(str, static_cast(end - str)); break; } case booleanValue: @@ -420,8 +427,7 @@ void FastWriter::writeValue(const Value& value) { const String& name = *it; if (it != members.begin()) document_ += ','; - document_ += valueToQuotedStringN(name.data(), - static_cast(name.length())); + document_ += valueToQuotedStringN(name.data(), name.length()); document_ += yamlCompatibilityEnabled_ ? ": " : ":"; writeValue(value[name]); } @@ -466,7 +472,7 @@ void StyledWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -489,7 +495,7 @@ void StyledWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); document_ += " : "; writeValue(childValue); if (++it == members.end()) { @@ -507,7 +513,7 @@ void StyledWriter::writeValue(const Value& value) { } void StyledWriter::writeArrayValue(const Value& value) { - unsigned size = value.size(); + size_t size = value.size(); if (size == 0) pushValue("[]"); else { @@ -516,7 +522,7 @@ void StyledWriter::writeArrayValue(const Value& value) { writeWithIndent("["); indent(); bool hasChildValue = !childValues_.empty(); - unsigned index = 0; + ArrayIndex index = 0; for (;;) { const Value& childValue = value[index]; writeCommentBeforeValue(childValue); @@ -539,7 +545,7 @@ void StyledWriter::writeArrayValue(const Value& value) { { assert(childValues_.size() == size); document_ += "[ "; - for (unsigned index = 0; index < size; ++index) { + for (size_t index = 0; index < size; ++index) { if (index > 0) document_ += ", "; document_ += childValues_[index]; @@ -684,7 +690,7 @@ void StyledStreamWriter::writeValue(const Value& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str))); + pushValue(valueToQuotedStringN(str, static_cast(end - str))); else pushValue(""); break; @@ -707,7 +713,7 @@ void StyledStreamWriter::writeValue(const Value& value) { const String& name = *it; const Value& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedString(name.c_str())); + writeWithIndent(valueToQuotedString(name.c_str(), name.size())); *document_ << " : "; writeValue(childValue); if (++it == members.end()) { @@ -958,8 +964,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { char const* end; bool ok = value.getString(&str, &end); if (ok) - pushValue(valueToQuotedStringN(str, static_cast(end - str), - emitUTF8_)); + pushValue( + valueToQuotedStringN(str, static_cast(end - str), emitUTF8_)); else pushValue(""); break; @@ -982,8 +988,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { String const& name = *it; Value const& childValue = value[name]; writeCommentBeforeValue(childValue); - writeWithIndent(valueToQuotedStringN( - name.data(), static_cast(name.length()), emitUTF8_)); + writeWithIndent( + valueToQuotedStringN(name.data(), name.length(), emitUTF8_)); *sout_ << colonSymbol_; writeValue(childValue); if (++it == members.end()) { @@ -1243,7 +1249,7 @@ String writeString(StreamWriter::Factory const& factory, Value const& root) { OStringStream sout; StreamWriterPtr const writer(factory.newStreamWriter()); writer->write(root, &sout); - return sout.str(); + return std::move(sout).str(); } OStream& operator<<(OStream& sout, Value const& root) { diff --git a/src/test_lib_json/jsontest.h b/src/test_lib_json/jsontest.h index cc05aef..834c58a 100644 --- a/src/test_lib_json/jsontest.h +++ b/src/test_lib_json/jsontest.h @@ -74,7 +74,7 @@ public: /// Removes the last PredicateContext added to the predicate stack /// chained list. - /// Next messages will be targed at the PredicateContext that was removed. + /// Next messages will be targeted at the PredicateContext that was removed. TestResult& popPredicateContext(); bool failed() const; diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index f296923..55ab224 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -12,6 +12,7 @@ #include "fuzz.h" #include "jsontest.h" +#include #include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include using CharReaderPtr = std::unique_ptr; @@ -218,11 +220,20 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, objects) { JSONTEST_ASSERT(foundId != nullptr); JSONTEST_ASSERT_EQUAL(Json::Value(1234), *foundId); + const std::string stringIdKey = "id"; + const Json::Value* stringFoundId = object1_.find(stringIdKey); + JSONTEST_ASSERT(stringFoundId != nullptr); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), *stringFoundId); + const char unknownIdKey[] = "unknown id"; const Json::Value* foundUnknownId = object1_.find(unknownIdKey, unknownIdKey + strlen(unknownIdKey)); JSONTEST_ASSERT_EQUAL(nullptr, foundUnknownId); + const std::string stringUnknownIdKey = "unknown id"; + const Json::Value* stringFoundUnknownId = object1_.find(stringUnknownIdKey); + JSONTEST_ASSERT_EQUAL(nullptr, stringFoundUnknownId); + // Access through demand() const char yetAnotherIdKey[] = "yet another id"; const Json::Value* foundYetAnotherId = @@ -308,10 +319,14 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, arrays) { const Json::Value& constArray = array1_; JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray[index0]); JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray[0]); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray.front()); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), constArray.back()); // Access through non-const reference JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_[index0]); JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_[0]); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_.front()); + JSONTEST_ASSERT_EQUAL(Json::Value(1234), array1_.back()); array1_[2] = Json::Value(17); JSONTEST_ASSERT_EQUAL(Json::Value(), array1_[1]); @@ -347,6 +362,19 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, resizeArray) { JSONTEST_ASSERT_EQUAL(array.size(), 0); } } + +JSONTEST_FIXTURE_LOCAL(ValueTest, resizePopulatesAllMissingElements) { + Json::ArrayIndex n = 10; + Json::Value v; + v.resize(n); + JSONTEST_ASSERT_EQUAL(n, v.size()); + JSONTEST_ASSERT_EQUAL(n, std::distance(v.begin(), v.end())); + JSONTEST_ASSERT_EQUAL(v.front(), Json::Value{}); + JSONTEST_ASSERT_EQUAL(v.back(), Json::Value{}); + for (const Json::Value& e : v) + JSONTEST_ASSERT_EQUAL(e, Json::Value{}); +} + JSONTEST_FIXTURE_LOCAL(ValueTest, getArrayValue) { Json::Value array; for (Json::ArrayIndex i = 0; i < 5; i++) @@ -393,6 +421,8 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, arrayInsertAtRandomIndex) { JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[0]); // check append JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[1]); JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[2]); + JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); // insert lvalue at the head JSONTEST_ASSERT(array.insert(0, str1)); @@ -400,6 +430,8 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, arrayInsertAtRandomIndex) { JSONTEST_ASSERT_EQUAL(Json::Value("index0"), array[1]); JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[2]); JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[3]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); // checking address for (Json::ArrayIndex i = 0; i < 3; i++) { JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); @@ -412,6 +444,8 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, arrayInsertAtRandomIndex) { JSONTEST_ASSERT_EQUAL(Json::Value("index4"), array[2]); JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[3]); JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[4]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array.back()); // checking address for (Json::ArrayIndex i = 0; i < 4; i++) { JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); @@ -425,6 +459,8 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, arrayInsertAtRandomIndex) { JSONTEST_ASSERT_EQUAL(Json::Value("index1"), array[3]); JSONTEST_ASSERT_EQUAL(Json::Value("index2"), array[4]); JSONTEST_ASSERT_EQUAL(Json::Value("index5"), array[5]); + JSONTEST_ASSERT_EQUAL(Json::Value("index3"), array.front()); + JSONTEST_ASSERT_EQUAL(Json::Value("index5"), array.back()); // checking address for (Json::ArrayIndex i = 0; i < 5; i++) { JSONTEST_ASSERT_EQUAL(vec[i], &array[i]); @@ -2005,6 +2041,34 @@ JSONTEST_FIXTURE_LOCAL(ValueTest, precision) { result = Json::writeString(b, v); JSONTEST_ASSERT_STRING_EQUAL(expected, result); + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 123.56345694873740545068; + expected = "124"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 1; + b.settings_["precisionType"] = "decimal"; + v = 1230.001; + expected = "1230.0"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 1230.001; + expected = "1230"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 0; + b.settings_["precisionType"] = "decimal"; + v = 1231.5; + expected = "1232"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + b.settings_["precision"] = 10; b.settings_["precisionType"] = "decimal"; v = 0.23300000; @@ -3577,12 +3641,12 @@ JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, issue209) { for (const auto& td : test_data) { bool ok = reader->parse(&*td.in.begin(), &*td.in.begin() + td.in.size(), &root, &errs); - JSONTEST_ASSERT(td.ok == ok) << "line:" << td.line << "\n" - << " expected: {" - << "ok:" << td.ok << ", in:\'" << td.in << "\'" - << "}\n" - << " actual: {" - << "ok:" << ok << "}\n"; + // clang-format off + JSONTEST_ASSERT(td.ok == ok) << + "line:" << td.line << "\n " << + "expected: {ok:" << td.ok << ", in:\'" << td.in << "\'}\n " << + "actual: {ok:" << ok << "}\n"; + // clang-format on } { @@ -3862,6 +3926,36 @@ JSONTEST_FIXTURE_LOCAL(FuzzTest, fuzzDoesntCrash) { example.size())); } +struct ParseWithStructuredErrorsTest : JsonTest::TestCase { + void testErrors( + const std::string& doc, bool success, + const std::vector& expectedErrors) { + Json::CharReaderBuilder b; + CharReaderPtr reader(b.newCharReader()); + Json::Value root; + JSONTEST_ASSERT_EQUAL( + reader->parse(doc.data(), doc.data() + doc.length(), &root, nullptr), + success); + auto actualErrors = reader->getStructuredErrors(); + JSONTEST_ASSERT_EQUAL(expectedErrors.size(), actualErrors.size()); + for (std::size_t i = 0; i < actualErrors.size(); i++) { + const auto& a = actualErrors[i]; + const auto& e = expectedErrors[i]; + JSONTEST_ASSERT_EQUAL(a.offset_start, e.offset_start); + JSONTEST_ASSERT_EQUAL(a.offset_limit, e.offset_limit); + JSONTEST_ASSERT_STRING_EQUAL(a.message, e.message); + } + } +}; + +JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, success) { + testErrors("{}", true, {}); +} + +JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, singleError) { + testErrors("{ 1 : 2 }", false, {{2, 3, "Missing '}' or object member name"}}); +} + int main(int argc, const char* argv[]) { JsonTest::Runner runner; diff --git a/test/data/fail_strict_comment_01.json b/test/data/fail_strict_comment_01.json new file mode 100644 index 0000000..b7e0a5e --- /dev/null +++ b/test/data/fail_strict_comment_01.json @@ -0,0 +1,4 @@ +{ + "a": "aaa", + "b": "bbb" // comments not allowed in strict mode +} diff --git a/test/data/fail_strict_comment_02.json b/test/data/fail_strict_comment_02.json new file mode 100644 index 0000000..699a7f7 --- /dev/null +++ b/test/data/fail_strict_comment_02.json @@ -0,0 +1,4 @@ +{ + "a": "aaa", // comments not allowed in strict mode + "b": "bbb" +} diff --git a/test/data/fail_strict_comment_03.json b/test/data/fail_strict_comment_03.json new file mode 100644 index 0000000..5f0fabf --- /dev/null +++ b/test/data/fail_strict_comment_03.json @@ -0,0 +1,3 @@ +{ + "array" : [1, 2, 3 /* comments not allowed in strict mode */] +} diff --git a/test/data/fail_test_object_02.json b/test/data/fail_test_object_02.json new file mode 100644 index 0000000..afe62c5 --- /dev/null +++ b/test/data/fail_test_object_02.json @@ -0,0 +1 @@ +{"one": 1 /* } */ { "two" : 2 } diff --git a/test/data/legacy_test_array_06.json b/test/data/legacy_test_array_06.json index 7f6c516..1fda03b 100644 --- a/test/data/legacy_test_array_06.json +++ b/test/data/legacy_test_array_06.json @@ -1,4 +1,4 @@ -[ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +[ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "ccccccccccccccccccccccc", "dddddddddddddddddddddddddddddddddddddddddddddddddddd" ] \ No newline at end of file diff --git a/test/data/legacy_test_complex_01.json b/test/data/legacy_test_complex_01.json index cc0f30f..2c4a869 100644 --- a/test/data/legacy_test_complex_01.json +++ b/test/data/legacy_test_complex_01.json @@ -1,17 +1,17 @@ -{ +{ "count" : 1234, "name" : { "aka" : "T.E.S.T.", "id" : 123987 }, - "attribute" : [ - "random", - "short", - "bold", - 12, - { "height" : 7, "width" : 64 } + "attribute" : [ + "random", + "short", + "bold", + 12, + { "height" : 7, "width" : 64 } ], - "test": { "1" : - { "2" : - { "3" : { "coord" : [ 1,2] } - } + "test": { "1" : + { "2" : + { "3" : { "coord" : [ 1,2] } + } } } } diff --git a/test/data/legacy_test_object_03.json b/test/data/legacy_test_object_03.json index 4fcd4d8..90dba2a 100644 --- a/test/data/legacy_test_object_03.json +++ b/test/data/legacy_test_object_03.json @@ -1,4 +1,4 @@ -{ +{ "count" : 1234, "name" : "test", "attribute" : "random" diff --git a/test/data/legacy_test_object_04.json b/test/data/legacy_test_object_04.json index 450762d..9e43ff8 100644 --- a/test/data/legacy_test_object_04.json +++ b/test/data/legacy_test_object_04.json @@ -1,3 +1,3 @@ -{ +{ "" : 1234 } diff --git a/test/data/legacy_test_preserve_comment_01.expected b/test/data/legacy_test_preserve_comment_01.expected index 2797aa7..d6c11b4 100644 --- a/test/data/legacy_test_preserve_comment_01.expected +++ b/test/data/legacy_test_preserve_comment_01.expected @@ -6,6 +6,6 @@ /* Comment before 'second' */ .second=2 -/* A comment at +/* A comment at the end of the file. */ diff --git a/test/data/legacy_test_preserve_comment_01.json b/test/data/legacy_test_preserve_comment_01.json index fabd55d..21b5ea7 100644 --- a/test/data/legacy_test_preserve_comment_01.json +++ b/test/data/legacy_test_preserve_comment_01.json @@ -9,6 +9,6 @@ "second" : 2 } -/* A comment at +/* A comment at the end of the file. */ diff --git a/test/data/legacy_test_real_13.expected b/test/data/legacy_test_real_13.expected new file mode 100644 index 0000000..8d3f03f --- /dev/null +++ b/test/data/legacy_test_real_13.expected @@ -0,0 +1,3 @@ +.=[] +.[0]=-inf +.[1]=inf diff --git a/test/data/legacy_test_real_13.json b/test/data/legacy_test_real_13.json new file mode 100644 index 0000000..287258a --- /dev/null +++ b/test/data/legacy_test_real_13.json @@ -0,0 +1 @@ +[-1e+9999, 1e+9999] diff --git a/test/pyjsontestrunner.py b/test/pyjsontestrunner.py index bd749b5..8acdbd2 100644 --- a/test/pyjsontestrunner.py +++ b/test/pyjsontestrunner.py @@ -15,7 +15,7 @@ import types if len(sys.argv) != 2: print("Usage: %s input-json-file", sys.argv[0]) sys.exit(3) - + input_path = sys.argv[1] base_path = os.path.splitext(input_path)[0] actual_path = base_path + '.actual' @@ -23,7 +23,7 @@ rewrite_path = base_path + '.rewrite' rewrite_actual_path = base_path + '.actual-rewrite' def valueTreeToString(fout, value, path = '.'): - ty = type(value) + ty = type(value) if ty is types.DictType: fout.write('%s={}\n' % path) suffix = path[-1] != '.' and '.' or '' @@ -49,7 +49,7 @@ def valueTreeToString(fout, value, path = '.'): fout.write('%s=null\n' % path) else: assert False and "Unexpected value type" - + def parseAndSaveValueTree(input, actual_path): root = json.loads(input) fout = file(actual_path, 'wt') @@ -62,7 +62,7 @@ def rewriteValueTree(value, rewrite_path): #rewrite = rewrite[1:-1] # Somehow the string is quoted ! jsonpy bug ? file(rewrite_path, 'wt').write(rewrite + '\n') return rewrite - + input = file(input_path, 'rt').read() root = parseAndSaveValueTree(input, actual_path) rewrite = rewriteValueTree(json.write(root), rewrite_path) diff --git a/test/runjsontests.py b/test/runjsontests.py index 5496e2c..49cc7a9 100644 --- a/test/runjsontests.py +++ b/test/runjsontests.py @@ -97,14 +97,17 @@ def runAllTests(jsontest_executable_path, input_dir = None, valgrind_path = use_valgrind and VALGRIND_CMD or '' for input_path in tests + test_jsonchecker: expect_failure = os.path.basename(input_path).startswith('fail') - is_json_checker_test = (input_path in test_jsonchecker) or expect_failure + is_json_checker_test = input_path in test_jsonchecker + is_parse_only = is_json_checker_test or expect_failure + is_strict_test = ('_strict_' in os.path.basename(input_path)) or is_json_checker_test print('TESTING:', input_path, end=' ') - options = is_json_checker_test and '--json-checker' or '' + options = is_parse_only and '--parse-only' or '' + options += is_strict_test and ' --strict' or '' options += ' --json-writer %s'%writerClass cmd = '%s%s %s "%s"' % ( valgrind_path, jsontest_executable_path, options, input_path) status, process_output = getStatusOutput(cmd) - if is_json_checker_test: + if is_parse_only: if expect_failure: if not status: print('FAILED')